Skip to content

Commit 97b30b5

Browse files
author
Sandra Thieme
committed
Resolve addresses by three word addresses
1 parent 079a45f commit 97b30b5

16 files changed

+453
-7
lines changed

src/main/java/net/contargo/iris/address/api/AddressApiController.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,12 @@
3232
import java.util.ArrayList;
3333
import java.util.List;
3434
import java.util.Map;
35+
import java.util.Optional;
3536

3637
import javax.servlet.http.HttpServletRequest;
3738

39+
import static net.contargo.iris.address.w3w.ThreeWordMatcher.isThreeWordAddress;
40+
3841
import static org.slf4j.LoggerFactory.getLogger;
3942

4043
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@@ -139,6 +142,15 @@ public ListOfAddressListsResponse addressesByAddressDetails(@RequestParam(requir
139142
}
140143
}
141144

145+
if (isThreeWordAddress(street)) {
146+
Optional<AddressDto> addressDto = addressDtoService.getAddressesByThreeWords(street.trim());
147+
148+
if (addressDto.isPresent()) {
149+
addressListDtos = singletonList(new AddressListDto("Result for three words " + street,
150+
singletonList(addressDto.get())));
151+
}
152+
}
153+
142154
Map<String, String> addressDetails = NominatimUtil.parameterMap(street, postalCode, city, country, name);
143155

144156
if (addressListDtos.isEmpty()) {

src/main/java/net/contargo/iris/address/dto/AddressDtoService.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import java.util.List;
66
import java.util.Map;
7+
import java.util.Optional;
78

89

910
/**
@@ -96,4 +97,14 @@ public interface AddressDtoService {
9697
* @return a list of matching addresses
9798
*/
9899
List<AddressDto> getAddressesByQuery(String query);
100+
101+
102+
/**
103+
* Returns an optional {@link AddressDto} matching the given three word address.
104+
*
105+
* @param threeWords a three word address
106+
*
107+
* @return an optional address
108+
*/
109+
Optional<AddressDto> getAddressesByThreeWords(String threeWords);
99110
}

src/main/java/net/contargo/iris/address/dto/AddressDtoServiceImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.util.ArrayList;
99
import java.util.List;
1010
import java.util.Map;
11+
import java.util.Optional;
1112

1213
import static java.util.Collections.singletonList;
1314
import static java.util.stream.Collectors.toList;
@@ -101,4 +102,13 @@ public List<AddressDto> getAddressesByQuery(String query) {
101102

102103
return addressServiceWrapper.getAddressesByQuery(query).stream().map(AddressDto::new).collect(toList());
103104
}
105+
106+
107+
@Override
108+
public Optional<AddressDto> getAddressesByThreeWords(String threeWords) {
109+
110+
Optional<Address> optionalAddress = addressServiceWrapper.getAddressByThreeWords(threeWords);
111+
112+
return optionalAddress.map(AddressDto::new);
113+
}
104114
}

src/main/java/net/contargo/iris/address/service/AddressServiceWrapper.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import net.contargo.iris.address.staticsearch.StaticAddress;
88
import net.contargo.iris.address.staticsearch.service.StaticAddressNotFoundException;
99
import net.contargo.iris.address.staticsearch.service.StaticAddressService;
10+
import net.contargo.iris.address.w3w.ThreeWordClient;
11+
import net.contargo.iris.address.w3w.ThreeWordClientException;
1012
import net.contargo.iris.normalizer.NormalizerService;
1113

1214
import org.apache.commons.lang.StringUtils;
@@ -19,6 +21,7 @@
1921
import java.util.Collections;
2022
import java.util.List;
2123
import java.util.Map;
24+
import java.util.Optional;
2225
import java.util.regex.Pattern;
2326

2427
import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY;
@@ -46,14 +49,16 @@ public class AddressServiceWrapper {
4649
private final StaticAddressService staticAddressService;
4750
private final AddressCache addressCache;
4851
private final NormalizerService normalizerService;
52+
private final ThreeWordClient threeWordClient;
4953

5054
public AddressServiceWrapper(AddressService addressService, StaticAddressService staticAddressService,
51-
AddressCache cache, NormalizerService normalizerService) {
55+
AddressCache cache, NormalizerService normalizerService, ThreeWordClient threeWordClient) {
5256

5357
this.addressService = addressService;
5458
this.staticAddressService = staticAddressService;
5559
this.addressCache = cache;
5660
this.normalizerService = normalizerService;
61+
this.threeWordClient = threeWordClient;
5762
}
5863

5964
/**
@@ -225,4 +230,19 @@ public List<Address> getAddressesByQuery(String query) {
225230

226231
return addresses;
227232
}
233+
234+
235+
@SuppressWarnings("squid:S1166")
236+
public Optional<Address> getAddressByThreeWords(String threeWords) {
237+
238+
try {
239+
GeoLocation resolvedLocation = threeWordClient.resolve(threeWords);
240+
241+
return Optional.of(getAddressForGeoLocation(resolvedLocation));
242+
} catch (ThreeWordClientException e) {
243+
LOG.info("Cannot resolve three word address {}: {}", threeWords, e.getMessage());
244+
245+
return Optional.empty();
246+
}
247+
}
228248
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package net.contargo.iris.address.w3w;
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
6+
import net.contargo.iris.GeoLocation;
7+
8+
import java.math.BigDecimal;
9+
10+
11+
/**
12+
* @author Sandra Thieme - [email protected]
13+
*/
14+
class ForwardW3wResponse {
15+
16+
private final W3wResponseGeometry geometry;
17+
private final W3wResponseStatus status;
18+
19+
@JsonCreator
20+
ForwardW3wResponse(@JsonProperty("geometry") W3wResponseGeometry geometry,
21+
@JsonProperty("status") W3wResponseStatus status) {
22+
23+
this.geometry = geometry;
24+
this.status = status;
25+
}
26+
27+
GeoLocation toGeolocation() {
28+
29+
return new GeoLocation(geometry.lat, geometry.lon);
30+
}
31+
32+
33+
boolean error() {
34+
35+
return status.code != null;
36+
}
37+
38+
39+
Integer errorCode() {
40+
41+
return status.code;
42+
}
43+
44+
45+
String errorMessage() {
46+
47+
return status.message;
48+
}
49+
50+
private static class W3wResponseGeometry {
51+
52+
private final BigDecimal lat;
53+
private final BigDecimal lon;
54+
55+
@JsonCreator
56+
private W3wResponseGeometry(@JsonProperty("lat") BigDecimal lat,
57+
@JsonProperty("lng") BigDecimal lon) {
58+
59+
this.lat = lat;
60+
this.lon = lon;
61+
}
62+
}
63+
64+
private static class W3wResponseStatus {
65+
66+
private final Integer code;
67+
private final String message;
68+
69+
@JsonCreator
70+
private W3wResponseStatus(@JsonProperty("code") Integer code,
71+
@JsonProperty("message") String message) {
72+
73+
this.code = code;
74+
this.message = message;
75+
}
76+
}
77+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package net.contargo.iris.address.w3w;
2+
3+
import net.contargo.iris.GeoLocation;
4+
5+
import org.springframework.http.ResponseEntity;
6+
7+
import org.springframework.web.client.RestTemplate;
8+
9+
10+
/**
11+
* @author Sandra Thieme - [email protected]
12+
*/
13+
public class ThreeWordClient {
14+
15+
private static final String FORWARD_URL =
16+
"https://api.what3words.com/v2/forward?addr={w3wAddress}&key={apiKey}&lang=de";
17+
18+
private final RestTemplate restTemplate;
19+
private final String apiKey;
20+
21+
public ThreeWordClient(RestTemplate restTemplate, String apiKey) {
22+
23+
this.restTemplate = restTemplate;
24+
this.apiKey = apiKey;
25+
}
26+
27+
public GeoLocation resolve(String threeWords) {
28+
29+
ResponseEntity<ForwardW3wResponse> response = restTemplate.getForEntity(FORWARD_URL, ForwardW3wResponse.class,
30+
threeWords, apiKey);
31+
32+
checkErrorStatus(response.getBody(), threeWords);
33+
34+
return response.getBody().toGeolocation();
35+
}
36+
37+
38+
private static void checkErrorStatus(ForwardW3wResponse response, String threeWords) {
39+
40+
if (response.error()) {
41+
Integer code = response.errorCode();
42+
String message = response.errorMessage();
43+
44+
throw new ThreeWordClientException(code, message, threeWords);
45+
}
46+
}
47+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package net.contargo.iris.address.w3w;
2+
3+
/**
4+
* @author Sandra Thieme - [email protected]
5+
*/
6+
public class ThreeWordClientException extends RuntimeException {
7+
8+
ThreeWordClientException(Integer code, String message, String threeWords) {
9+
10+
super("API of w3w returned error code " + code + " with message '" + message
11+
+ "' for three word address '" + threeWords + "'");
12+
}
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package net.contargo.iris.address.w3w;
2+
3+
import java.util.regex.Pattern;
4+
5+
6+
/**
7+
* @author Sandra Thieme - [email protected]
8+
*/
9+
public class ThreeWordMatcher {
10+
11+
private static final Pattern THREE_WORD_PATTERN = Pattern.compile("^\\p{L}+\\.\\p{L}+\\.\\p{L}+$");
12+
13+
public static boolean isThreeWordAddress(String input) {
14+
15+
return input != null && THREE_WORD_PATTERN.matcher(input.trim()).matches();
16+
}
17+
}

src/main/resources/application-context.xml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,16 @@
120120
</bean>
121121
</constructor-arg>
122122
<constructor-arg name="normalizerService" ref="normalizerService"/>
123+
<constructor-arg name="threeWordClient" ref="threeWordClient"/>
123124
</bean>
124-
125+
126+
<bean id="threeWordClient" class="net.contargo.iris.address.w3w.ThreeWordClient">
127+
<constructor-arg>
128+
<bean class="org.springframework.web.client.RestTemplate"/>
129+
</constructor-arg>
130+
<constructor-arg name="apiKey" value="${w3w.apikey}"/>
131+
</bean>
132+
125133
<bean id="addressListFilter" class="net.contargo.iris.address.service.AddressListFilterImpl"/>
126134

127135
<bean id="addressDtoService" class="net.contargo.iris.address.dto.AddressDtoServiceImpl">
@@ -401,7 +409,7 @@
401409
</bean>
402410
</constructor-arg>
403411
</bean>
404-
412+
405413
<bean id="postcodeCitySuburbStaticAddressMappingProcessor" class="net.contargo.iris.address.staticsearch.service.PostcodeCitySuburbStaticAddressMappingProcessor">
406414
<constructor-arg name="next" ref="postcodeCityStaticAddressMappingProcessor"/>
407415
<constructor-arg name="normalizerService" ref="normalizerService"/>

src/main/resources/application.properties

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ [email protected]
4545
addresses.csvdir=csv/
4646
addresses.cron=0 * * * * ?
4747

48-
feature.dtruck=false
48+
feature.dtruck=false
49+
50+
w3w.apikey=

src/test/java/net/contargo/iris/address/api/AddressApiControllerMvcUnitTest.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
import java.util.HashMap;
3232
import java.util.Map;
33+
import java.util.Optional;
3334

3435
import static org.hamcrest.MatcherAssert.assertThat;
3536

@@ -169,6 +170,32 @@ public void addressesByAddressDetailsWithHashKey() throws Exception {
169170
}
170171

171172

173+
@Test
174+
public void addressesByAddressDetailsWithThreeWords() throws Exception {
175+
176+
AddressDto addressDto = new AddressDto(new Address("Resolved address"));
177+
when(addressDtoServiceMock.getAddressesByThreeWords("one.two.three")).thenReturn(Optional.of(addressDto));
178+
179+
ResultActions resultActions = mockMvc.perform(get("/geocodes/?street=one.two.three").accept(APPLICATION_JSON));
180+
resultActions.andExpect(status().isOk());
181+
resultActions.andExpect(content().contentType("application/json"));
182+
resultActions.andExpect(jsonPath("$.geoCodeResponse.addresses[0].addresses[0].displayName").value(
183+
"Resolved address"));
184+
}
185+
186+
187+
@Test
188+
public void addressesByAddressDetailsWithFailure() throws Exception {
189+
190+
when(addressDtoServiceMock.getAddressesByThreeWords("one.two.three")).thenReturn(Optional.empty());
191+
192+
ResultActions resultActions = mockMvc.perform(get("/geocodes/?street=one.two.three").accept(APPLICATION_JSON));
193+
resultActions.andExpect(status().isOk());
194+
resultActions.andExpect(content().contentType("application/json"));
195+
resultActions.andExpect(jsonPath("$.geoCodeResponse.addresses").isEmpty());
196+
}
197+
198+
172199
@Test
173200
public void addressesByAddressDetailsWithHashKeyAndException() throws Exception {
174201

src/test/java/net/contargo/iris/address/dto/AddressDtoServiceImplUnitTest.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.util.HashMap;
1515
import java.util.List;
1616
import java.util.Map;
17+
import java.util.Optional;
1718

1819
import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY;
1920
import static net.contargo.iris.address.nominatim.service.AddressDetailKey.COUNTRY;
@@ -192,4 +193,16 @@ public void getAddressesByQuery() {
192193
assertThat(addresses, hasSize(1));
193194
assertThat(addresses.get(0).getDisplayName(), is("Gartenstr. 67, Karlsruhe (Südweststadt)"));
194195
}
196+
197+
198+
@Test
199+
public void getAddressesByThreeWords() {
200+
201+
when(addressServiceWrapperMock.getAddressByThreeWords("riches.lofts.guessing")).thenReturn(Optional.of(
202+
new Address("German Chancellery")));
203+
204+
Optional<AddressDto> dto = sut.getAddressesByThreeWords("riches.lofts.guessing");
205+
206+
assertThat(dto.get().getDisplayName(), is("German Chancellery"));
207+
}
195208
}

0 commit comments

Comments
 (0)