Skip to content

Commit a784169

Browse files
authored
Merge pull request #197 from Esri/tschie/update-find-place
Update find place to use MVC pattern
2 parents 24291bb + 514328c commit a784169

File tree

3 files changed

+449
-436
lines changed

3 files changed

+449
-436
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
/*
2+
* Copyright 2017 Esri.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
17+
package com.esri.samples.search.find_place;
18+
19+
import java.util.ArrayList;
20+
import java.util.HashMap;
21+
import java.util.List;
22+
import java.util.concurrent.ExecutionException;
23+
import java.util.stream.Collectors;
24+
25+
import javafx.application.Platform;
26+
import javafx.fxml.FXML;
27+
import javafx.geometry.Point2D;
28+
import javafx.scene.control.Button;
29+
import javafx.scene.control.ComboBox;
30+
import javafx.scene.image.Image;
31+
import javafx.scene.input.KeyEvent;
32+
import javafx.scene.input.MouseButton;
33+
import javafx.util.Duration;
34+
35+
import com.esri.arcgisruntime.concurrent.ListenableFuture;
36+
import com.esri.arcgisruntime.geometry.Envelope;
37+
import com.esri.arcgisruntime.geometry.Point;
38+
import com.esri.arcgisruntime.mapping.ArcGISMap;
39+
import com.esri.arcgisruntime.mapping.Basemap;
40+
import com.esri.arcgisruntime.mapping.Viewpoint;
41+
import com.esri.arcgisruntime.mapping.view.Callout;
42+
import com.esri.arcgisruntime.mapping.view.Graphic;
43+
import com.esri.arcgisruntime.mapping.view.GraphicsOverlay;
44+
import com.esri.arcgisruntime.mapping.view.IdentifyGraphicsOverlayResult;
45+
import com.esri.arcgisruntime.mapping.view.MapView;
46+
import com.esri.arcgisruntime.mapping.view.ViewpointChangedEvent;
47+
import com.esri.arcgisruntime.mapping.view.ViewpointChangedListener;
48+
import com.esri.arcgisruntime.mapping.view.WrapAroundMode;
49+
import com.esri.arcgisruntime.symbology.PictureMarkerSymbol;
50+
import com.esri.arcgisruntime.tasks.geocode.GeocodeParameters;
51+
import com.esri.arcgisruntime.tasks.geocode.GeocodeResult;
52+
import com.esri.arcgisruntime.tasks.geocode.LocatorTask;
53+
import com.esri.arcgisruntime.tasks.geocode.SuggestParameters;
54+
import com.esri.arcgisruntime.tasks.geocode.SuggestResult;
55+
56+
public class FindPlaceController {
57+
58+
@FXML private ComboBox<String> locationBox;
59+
@FXML private MapView mapView;
60+
@FXML private ComboBox<String> placeBox;
61+
@FXML private Button redoButton;
62+
63+
private Callout callout;
64+
private GraphicsOverlay graphicsOverlay;
65+
private LocatorTask locatorTask;
66+
private PictureMarkerSymbol pinSymbol;
67+
68+
@FXML
69+
public void initialize() {
70+
71+
// create ArcGISMap with streets basemap and add it to map view
72+
ArcGISMap map = new ArcGISMap(Basemap.createStreets());
73+
mapView.setMap(map);
74+
mapView.setWrapAroundMode(WrapAroundMode.DISABLED);
75+
76+
// add a graphics overlay to the map view
77+
graphicsOverlay = new GraphicsOverlay();
78+
mapView.getGraphicsOverlays().add(graphicsOverlay);
79+
80+
// set the callout's default style
81+
callout = mapView.getCallout();
82+
callout.setLeaderPosition(Callout.LeaderPosition.BOTTOM);
83+
84+
// create a locatorTask task
85+
locatorTask = new LocatorTask("http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer");
86+
87+
// create a pin graphic
88+
Image img = new Image(getClass().getResourceAsStream("/symbols/pin.png"), 0, 80, true, true);
89+
pinSymbol = new PictureMarkerSymbol(img);
90+
pinSymbol.loadAsync();
91+
92+
// event to get auto-complete suggestions when the user types a place query
93+
placeBox.getEditor().setOnKeyTyped((KeyEvent evt) -> {
94+
95+
// get the search box text for auto-complete suggestions
96+
String typed = placeBox.getEditor().getText();
97+
98+
if (!"".equals(typed)) {
99+
100+
// suggest places only
101+
SuggestParameters geocodeParameters = new SuggestParameters();
102+
geocodeParameters.getCategories().add("POI");
103+
104+
// get suggestions from the locatorTask
105+
ListenableFuture<List<SuggestResult>> suggestions = locatorTask.suggestAsync(typed, geocodeParameters);
106+
107+
// add a listener to update suggestions list when loaded
108+
suggestions.addDoneListener(new SuggestionsLoadedListener(suggestions, placeBox));
109+
}
110+
});
111+
112+
// event to get auto-complete suggestions for location when the user types a search location
113+
locationBox.getEditor().setOnKeyTyped((KeyEvent evt) -> {
114+
115+
// get the search box text for auto-complete suggestions
116+
String typed = locationBox.getEditor().getText();
117+
118+
if (!typed.equals("")) {
119+
120+
// get suggestions from the locatorTask
121+
ListenableFuture<List<SuggestResult>> suggestions = locatorTask.suggestAsync(typed);
122+
123+
// add a listener to update suggestions list when loaded
124+
suggestions.addDoneListener(new SuggestionsLoadedListener(suggestions, locationBox));
125+
}
126+
});
127+
128+
// event to display a callout for a selected result
129+
mapView.setOnMouseClicked(evt -> {
130+
// check that the primary mouse button was clicked and the user is not panning
131+
if (evt.isStillSincePress() && evt.getButton() == MouseButton.PRIMARY) {
132+
// create a point from where the user clicked
133+
Point2D point = new Point2D(evt.getX(), evt.getY());
134+
135+
// get layers with elements near the clicked location
136+
ListenableFuture<IdentifyGraphicsOverlayResult> identifyResults = mapView.identifyGraphicsOverlayAsync(graphicsOverlay, point,
137+
10, false);
138+
identifyResults.addDoneListener(() -> {
139+
try {
140+
List<Graphic> graphics = identifyResults.get().getGraphics();
141+
if (graphics.size() > 0) {
142+
Graphic marker = graphics.get(0);
143+
// update the callout
144+
Platform.runLater(() -> {
145+
callout.setTitle(marker.getAttributes().get("title").toString());
146+
callout.setDetail(marker.getAttributes().get("detail").toString());
147+
callout.showCalloutAt((Point) marker.getGeometry(), new Point2D(0, -24), Duration.ZERO);
148+
});
149+
}
150+
} catch (Exception e) {
151+
e.printStackTrace();
152+
}
153+
});
154+
}
155+
});
156+
}
157+
158+
/**
159+
* Searches for places near the chosen location when the "search" button is clicked.
160+
*/
161+
@FXML
162+
private void search() {
163+
String placeQuery = placeBox.getEditor().getText();
164+
String locationQuery = locationBox.getEditor().getText();
165+
if (placeQuery != null && locationQuery != null && !"".equals(placeQuery) && !"".equals(locationQuery)) {
166+
GeocodeParameters geocodeParameters = new GeocodeParameters();
167+
geocodeParameters.getResultAttributeNames().add("*"); // return all attributes
168+
geocodeParameters.setOutputSpatialReference(mapView.getSpatialReference());
169+
170+
// run the locatorTask geocode task
171+
ListenableFuture<List<GeocodeResult>> results = locatorTask.geocodeAsync(locationQuery, geocodeParameters);
172+
results.addDoneListener(() -> {
173+
try {
174+
List<GeocodeResult> points = results.get();
175+
if (points.size() > 0) {
176+
// create a search area envelope around the location
177+
Point p = points.get(0).getDisplayLocation();
178+
Envelope preferredSearchArea = new Envelope(p.getX() - 10000, p.getY() - 10000, p.getX() + 10000, p.getY
179+
() + 10000, p.getSpatialReference());
180+
// set the geocode parameters search area to the envelope
181+
geocodeParameters.setSearchArea(preferredSearchArea);
182+
// zoom to the envelope
183+
mapView.setViewpointAsync(new Viewpoint(preferredSearchArea));
184+
// perform the geocode operation
185+
ListenableFuture<List<GeocodeResult>> geocodeTask = locatorTask.geocodeAsync(placeQuery,
186+
geocodeParameters);
187+
188+
// add a listener to display the results when loaded
189+
geocodeTask.addDoneListener(new ResultsLoadedListener(geocodeTask));
190+
}
191+
} catch (Exception e) {
192+
e.printStackTrace();
193+
}
194+
});
195+
}
196+
}
197+
198+
/**
199+
* Searches for places within the current map extent when the "redo search in this area" button is clicked.
200+
*/
201+
@FXML
202+
private void searchByCurrentViewpoint() {
203+
String placeQuery = placeBox.getEditor().getText();
204+
GeocodeParameters geocodeParameters = new GeocodeParameters();
205+
geocodeParameters.getResultAttributeNames().add("*"); // return all attributes
206+
geocodeParameters.setOutputSpatialReference(mapView.getSpatialReference());
207+
geocodeParameters.setSearchArea(mapView.getCurrentViewpoint(Viewpoint.Type.BOUNDING_GEOMETRY).getTargetGeometry());
208+
209+
//perform the geocode operation
210+
ListenableFuture<List<GeocodeResult>> geocodeTask = locatorTask.geocodeAsync(placeQuery, geocodeParameters);
211+
212+
// add a listener to display the results when loaded
213+
geocodeTask.addDoneListener(new ResultsLoadedListener(geocodeTask));
214+
}
215+
216+
/**
217+
* A listener to update a {@link ComboBox} when suggestions from a call to
218+
* {@link LocatorTask#suggestAsync(String, SuggestParameters)} are loaded.
219+
*/
220+
private class SuggestionsLoadedListener implements Runnable {
221+
222+
private final ListenableFuture<List<SuggestResult>> results;
223+
private ComboBox<String> comboBox;
224+
225+
/**
226+
* Constructs a listener to update an auto-complete list for geocode
227+
* suggestions.
228+
*
229+
* @param results suggestion results from a {@link LocatorTask}
230+
* @param box the {@link ComboBox} to update with the suggestions
231+
*/
232+
SuggestionsLoadedListener(ListenableFuture<List<SuggestResult>> results, ComboBox<String> box) {
233+
this.results = results;
234+
this.comboBox = box;
235+
}
236+
237+
@Override
238+
public void run() {
239+
240+
try {
241+
List<SuggestResult> suggestResult = results.get();
242+
List<String> suggestions = suggestResult.stream().map(SuggestResult::getLabel).collect(Collectors.toList());
243+
244+
// update the combo box with suggestions
245+
Platform.runLater(() -> {
246+
comboBox.getItems().clear();
247+
comboBox.getItems().addAll(suggestions);
248+
comboBox.show();
249+
});
250+
251+
} catch (InterruptedException | ExecutionException e) {
252+
e.printStackTrace();
253+
}
254+
}
255+
}
256+
257+
/**
258+
* Runnable listener to update marker and callout when new results are loaded.
259+
*/
260+
private class ResultsLoadedListener implements Runnable {
261+
262+
private final ListenableFuture<List<GeocodeResult>> results;
263+
264+
/**
265+
* Constructs a runnable listener for the geocode results.
266+
*
267+
* @param results results from a {@link LocatorTask#geocodeAsync} task
268+
*/
269+
ResultsLoadedListener(ListenableFuture<List<GeocodeResult>> results) {
270+
this.results = results;
271+
}
272+
273+
@Override
274+
public void run() {
275+
276+
// hide callout if showing
277+
mapView.getCallout().dismiss();
278+
279+
List<Graphic> markers = new ArrayList<>();
280+
try {
281+
List<GeocodeResult> geocodes = results.get();
282+
for (GeocodeResult geocode : geocodes) {
283+
284+
// get attributes from the result for the callout
285+
String addrType = geocode.getAttributes().get("Addr_type").toString();
286+
String placeName = geocode.getAttributes().get("PlaceName").toString();
287+
String placeAddr = geocode.getAttributes().get("Place_addr").toString();
288+
String matchAddr = geocode.getAttributes().get("Match_addr").toString();
289+
String locType = geocode.getAttributes().get("Type").toString();
290+
291+
// format callout details
292+
String title;
293+
String detail;
294+
switch (addrType) {
295+
case "POI":
296+
title = placeName.equals("") ? "" : placeName;
297+
if (!placeAddr.equals("")) {
298+
detail = placeAddr;
299+
} else if (!matchAddr.equals("") && !locType.equals("")) {
300+
detail = !matchAddr.contains(",") ? locType : matchAddr.substring(matchAddr.indexOf(", ") + 2);
301+
} else {
302+
detail = "";
303+
}
304+
break;
305+
case "StreetName":
306+
case "PointAddress":
307+
case "Postal":
308+
if (matchAddr.contains(",")) {
309+
title = matchAddr.equals("") ? "" : matchAddr.split(",")[0];
310+
detail = matchAddr.equals("") ? "" : matchAddr.substring(matchAddr.indexOf(", ") + 2);
311+
break;
312+
}
313+
default:
314+
title = "";
315+
detail = matchAddr.equals("") ? "" : matchAddr;
316+
break;
317+
}
318+
319+
HashMap<String, Object> attributes = new HashMap<>();
320+
attributes.put("title", title);
321+
attributes.put("detail", detail);
322+
323+
// create the marker
324+
Graphic marker = new Graphic(geocode.getDisplayLocation(), attributes, pinSymbol);
325+
markers.add(marker);
326+
}
327+
328+
// update the markers
329+
if (markers.size() > 0) {
330+
Platform.runLater(() -> {
331+
// clear out previous results
332+
graphicsOverlay.getGraphics().clear();
333+
placeBox.hide();
334+
335+
// add the markers to the graphics overlay
336+
graphicsOverlay.getGraphics().addAll(markers);
337+
338+
//reset redo search button
339+
redoButton.setDisable(true);
340+
341+
// listener to enable the redo-search button the first time the user moves away from the initial search area
342+
ViewpointChangedListener changedListener = new ViewpointChangedListener() {
343+
344+
@Override
345+
public void viewpointChanged(ViewpointChangedEvent arg0) {
346+
347+
redoButton.setDisable(false);
348+
mapView.removeViewpointChangedListener(this);
349+
}
350+
};
351+
352+
mapView.addViewpointChangedListener(changedListener);
353+
});
354+
}
355+
356+
} catch (InterruptedException | ExecutionException e) {
357+
e.printStackTrace();
358+
}
359+
}
360+
}
361+
362+
/**
363+
* Stops the animation and disposes of application resources.
364+
*/
365+
void terminate() {
366+
367+
if (mapView != null) {
368+
mapView.dispose();
369+
}
370+
}
371+
372+
}

0 commit comments

Comments
 (0)