Skip to content

Commit 69d1833

Browse files
Sandra Thiemeomessner
authored andcommitted
Resolve addresses by three word addresses
1 parent 6c17613 commit 69d1833

16 files changed

+451
-4
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
@@ -34,9 +34,12 @@
3434
import java.util.ArrayList;
3535
import java.util.List;
3636
import java.util.Map;
37+
import java.util.Optional;
3738

3839
import javax.servlet.http.HttpServletRequest;
3940

41+
import static net.contargo.iris.address.w3w.ThreeWordMatcher.isThreeWordAddress;
42+
4043
import static org.slf4j.LoggerFactory.getLogger;
4144

4245
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
@@ -144,6 +147,15 @@ public ListOfAddressListsResponse addressesByAddressDetails(@RequestParam(requir
144147
}
145148
}
146149

150+
if (isThreeWordAddress(street)) {
151+
Optional<AddressDto> addressDto = addressDtoService.getAddressesByThreeWords(street.trim());
152+
153+
if (addressDto.isPresent()) {
154+
addressListDtos = singletonList(new AddressListDto("Result for three words " + street,
155+
singletonList(addressDto.get())));
156+
}
157+
}
158+
147159
Map<String, String> addressDetails = NominatimUtil.parameterMap(street, postalCode, city, country, name);
148160

149161
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
@@ -5,6 +5,7 @@
55

66
import java.util.List;
77
import java.util.Map;
8+
import java.util.Optional;
89

910

1011
/**
@@ -116,4 +117,14 @@ public interface AddressDtoService {
116117
* @return a list of matching addresses
117118
*/
118119
List<AddressDto> getAddressesByQuery(String query);
120+
121+
122+
/**
123+
* Returns an optional {@link AddressDto} matching the given three word address.
124+
*
125+
* @param threeWords a three word address
126+
*
127+
* @return an optional address
128+
*/
129+
Optional<AddressDto> getAddressesByThreeWords(String threeWords);
119130
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.ArrayList;
1010
import java.util.List;
1111
import java.util.Map;
12+
import java.util.Optional;
1213

1314
import static java.util.Collections.singletonList;
1415
import static java.util.stream.Collectors.toList;
@@ -110,4 +111,13 @@ public List<AddressDto> getAddressesByQuery(String query) {
110111

111112
return addressServiceWrapper.getAddressesByQuery(query).stream().map(AddressDto::new).collect(toList());
112113
}
114+
115+
116+
@Override
117+
public Optional<AddressDto> getAddressesByThreeWords(String threeWords) {
118+
119+
Optional<Address> optionalAddress = addressServiceWrapper.getAddressByThreeWords(threeWords);
120+
121+
return optionalAddress.map(AddressDto::new);
122+
}
113123
}

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,16 @@
121121
</bean>
122122
</constructor-arg>
123123
<constructor-arg name="normalizerService" ref="normalizerService"/>
124+
<constructor-arg name="threeWordClient" ref="threeWordClient"/>
124125
</bean>
125126

127+
<bean id="threeWordClient" class="net.contargo.iris.address.w3w.ThreeWordClient">
128+
<constructor-arg>
129+
<bean class="org.springframework.web.client.RestTemplate"/>
130+
</constructor-arg>
131+
<constructor-arg name="apiKey" value="${w3w.apikey}"/>
132+
</bean>
133+
126134
<bean id="addressListFilter" class="net.contargo.iris.address.service.AddressListFilterImpl"/>
127135

128136
<bean id="addressDtoService" class="net.contargo.iris.address.dto.AddressDtoServiceImpl">

src/main/resources/application.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@ cookies.link=https://example.com
5454
routing.threads=10
5555

5656
routeDataRevision.mandatoryForSwissAddress=true
57+
58+
59+
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
@@ -15,6 +15,7 @@
1515
import java.util.HashMap;
1616
import java.util.List;
1717
import java.util.Map;
18+
import java.util.Optional;
1819

1920
import static net.contargo.iris.address.nominatim.service.AddressDetailKey.CITY;
2021
import static net.contargo.iris.address.nominatim.service.AddressDetailKey.COUNTRY;
@@ -211,4 +212,16 @@ public void getAddressesByQuery() {
211212
assertThat(addresses, hasSize(1));
212213
assertThat(addresses.get(0).getDisplayName(), is("Gartenstr. 67, Karlsruhe (Südweststadt)"));
213214
}
215+
216+
217+
@Test
218+
public void getAddressesByThreeWords() {
219+
220+
when(addressServiceWrapperMock.getAddressByThreeWords("riches.lofts.guessing")).thenReturn(Optional.of(
221+
new Address("German Chancellery")));
222+
223+
Optional<AddressDto> dto = sut.getAddressesByThreeWords("riches.lofts.guessing");
224+
225+
assertThat(dto.get().getDisplayName(), is("German Chancellery"));
226+
}
214227
}

0 commit comments

Comments
 (0)