1 package org.kuali.mobility.tours.service;
2
3 import java.util.ArrayList;
4 import java.util.Collections;
5 import java.util.Comparator;
6 import java.util.HashMap;
7 import java.util.Iterator;
8 import java.util.List;
9 import java.util.Map;
10
11 import javax.ws.rs.DELETE;
12 import javax.ws.rs.GET;
13 import javax.ws.rs.PUT;
14 import javax.ws.rs.Path;
15 import javax.ws.rs.QueryParam;
16
17 import org.apache.commons.lang3.StringEscapeUtils;
18 import org.kuali.mobility.configparams.service.ConfigParamService;
19 import org.kuali.mobility.math.geometry.Point;
20 import org.kuali.mobility.math.geometry.Spherical;
21 import org.kuali.mobility.security.authn.entity.User;
22 import org.kuali.mobility.tours.dao.ToursDao;
23 import org.kuali.mobility.tours.entity.POI;
24 import org.kuali.mobility.tours.entity.Tour;
25 import org.springframework.beans.factory.annotation.Autowired;
26
27 import de.micromata.opengis.kml.v_2_2_0.AltitudeMode;
28 import de.micromata.opengis.kml.v_2_2_0.Document;
29 import de.micromata.opengis.kml.v_2_2_0.Folder;
30 import de.micromata.opengis.kml.v_2_2_0.Kml;
31 import de.micromata.opengis.kml.v_2_2_0.KmlFactory;
32 import de.micromata.opengis.kml.v_2_2_0.LineString;
33 import de.micromata.opengis.kml.v_2_2_0.Style;
34 import de.micromata.opengis.kml.v_2_2_0.gx.FlyToMode;
35 import de.micromata.opengis.kml.v_2_2_0.gx.Playlist;
36
37 public class ToursServiceImpl implements ToursService {
38
39 private final double FLY_SPEED = 40;
40
41 @Autowired
42 private ConfigParamService configParamService;
43 public void setConfigParamService(ConfigParamService configParamService) {
44 this.configParamService = configParamService;
45 }
46
47 @Autowired
48 private ToursDao toursDao;
49 public void setConfigParamDao(ToursDao toursDao) {
50 this.toursDao = toursDao;
51 }
52
53 @GET
54 @Path("tour/search/byid")
55 @Override
56 public Tour findTourById(@QueryParam( value = "id") Long id) {
57 return toursDao.findTourById(id);
58 }
59
60 @GET
61 @Path("tour/search/byname")
62 @Override
63 public Tour findTourByName(@QueryParam( value = "name") String name) {
64 return toursDao.findTourByName(name);
65 }
66
67 @PUT
68 @Path("tour/save")
69 @Override
70 public Long saveTour(Tour tour) {
71 return toursDao.saveTour(tour);
72 }
73
74 @GET
75 @Path("tour/lookup")
76 @Override
77 public List<Tour> findAllTours() {
78 return toursDao.findAllTours();
79 }
80
81 @DELETE
82 @Path("tour/delete")
83 @Override
84 public void deleteTourById(@QueryParam(value = "id")Long id) {
85 toursDao.deleteTourById(id);
86 }
87
88 @PUT
89 @Path("tour/repeat")
90 @Override
91 public void duplicateTourById(@QueryParam(value = "id")Long id) {
92 Tour tour = toursDao.findTourById(id).copy(false);
93 tour.setName(tour.getName() + " (copy)");
94 toursDao.saveTour(tour);
95 }
96
97 @PUT
98 @Path("poi/repeat")
99 @Override
100 public void duplicatePoiById(Long id) {
101 POI poi = toursDao.findPoiById(id).copy(false);
102 poi.setName(poi.getName() + " (copy)");
103 toursDao.savePoi(poi);
104 }
105
106 @GET
107 @Path("poi/search/byid")
108 @Override
109 public POI findPoiById(@QueryParam (value = "id") Long id) {
110 return toursDao.findPoiById(id);
111 }
112
113 @GET
114 @Path("poi/search/byorder")
115 @Override
116 public POI findPoiByOrder(@QueryParam (value = "id") Long tourId, @QueryParam (value = "order") Integer order) {
117 return toursDao.findPoiByOrder(tourId, order);
118 }
119
120 @PUT
121 @Path("poi/save")
122 @Override
123 public Long savePoi(POI poi) {
124 return toursDao.savePoi(poi);
125 }
126
127 @DELETE
128 @Path("poi/delete")
129 @Override
130 public void deletePoiById(@QueryParam(value = "id") Long poiId) {
131 toursDao.deletePoiById(poiId);
132 }
133
134 @GET
135 @Path("poi/common")
136 @Override
137 public List<POI> findAllCommonPOI() {
138 return toursDao.findAllCommonPOI();
139 }
140
141 @Override
142 public boolean hasAccessToEditTour(User user, Tour tour) {
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158 return true;
159 }
160
161 @Override
162 public boolean hasAccessToViewTour(User user, Tour tour) {
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178 return true;
179 }
180
181 @Override
182 public boolean hasAccessToEditPOI(User user, POI poi) {
183
184
185
186
187
188
189
190
191
192
193
194 return true;
195 }
196
197 @Override
198 public boolean hasAccessToViewPOI(User user, POI poi) {
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214 return true;
215 }
216
217 @Override
218 public boolean hasAccessToPublish(User user) {
219
220
221
222
223
224 return true;
225 }
226
227 @Override
228 public Kml createTourKml(Tour tour) {
229 Kml kml = KmlFactory.createKml();
230 Document document = new Document();
231 kml.setFeature(document);
232
233 final Style style = document.createAndAddStyle().withId("PathStyle");
234 style.createAndSetLineStyle().withColor("FF0000FF").withWidth(3.0d);
235
236 final Folder folder = document.createAndAddFolder().withName("Points of Interest").withOpen(true).withDescription("The points of interest selected for this tour.");
237 int i=0;
238 for (POI poi : tour.getPointsOfInterest()) {
239 folder.createAndAddPlacemark()
240 .withId("poi" + i).withName(poi.getName()).withDescription(getPoiDescription(poi)).createAndSetPoint().addToCoordinates(poi.getLongitude()+","+poi.getLatitude()+",0");
241 i++;
242 }
243
244 List<Point> polyLine = decodePolyLine(tour.getPath());
245 LineString lineString = document.createAndAddPlacemark().withName(tour.getName()).withDescription(tour.getDescription()).withStyleUrl("PathStyle")
246 .createAndSetLineString().withTessellate(true).withAltitudeMode(AltitudeMode.CLAMP_TO_GROUND);
247 for (Point p : polyLine) {
248 lineString.addToCoordinates(p.getLongitude(), p.getLatitude());
249 }
250
251 Map<Integer, List<Integer>> poiPathIndices = findPoiTourIndices(polyLine, tour.getPointsOfInterest());
252 de.micromata.opengis.kml.v_2_2_0.gx.Tour kmlTour = document.createAndAddTour().withName(StringEscapeUtils.escapeHtml4(tour.getName()));
253 Playlist playlist = kmlTour.createAndSetPlaylist();
254
255 double heading = 0;
256 double flyTime = 3;
257 Point lastPoint = null;
258 int index = 0;
259 List<POI> pois = tour.getPointsOfInterest();
260 for (Point vertex : polyLine) {
261 if (lastPoint != null){
262 heading = Spherical.computeHeading(lastPoint, vertex);
263 flyTime = computeFlyTime(lastPoint, vertex);
264 buildFlyTo(playlist, vertex, heading, FlyToMode.SMOOTH, flyTime);
265 } else {
266 buildFlyTo(playlist, vertex, heading, FlyToMode.BOUNCE, flyTime);
267 }
268 lastPoint = vertex;
269
270 if (!poiPathIndices.isEmpty() && poiPathIndices.get(index) != null) {
271 List<Integer> poiIndices = poiPathIndices.get(index);
272 for (int poiIndex : poiIndices) {
273 POI poi = pois.get(poiIndex);
274 Point point = new Point(poi.getLatitude(), poi.getLongitude());
275 heading = Spherical.computeHeading(lastPoint, point);
276 flyTime = computeFlyTime(lastPoint, point);
277 buildFlyTo(playlist, point, heading, FlyToMode.SMOOTH, flyTime);
278
279 playlist.createAndAddWait();
280
281 lastPoint = point;
282 }
283 }
284 index++;
285 }
286 return kml;
287 }
288
289 private void buildFlyTo(Playlist playlist, Point point, double heading, FlyToMode mode, double duration) {
290 playlist.createAndAddFlyTo()
291 .withDuration(duration)
292 .withFlyToMode(mode)
293 .createAndSetLookAt()
294 .withAltitudeMode(AltitudeMode.CLAMP_TO_GROUND)
295 .withRange(100)
296 .withTilt(45)
297 .withHeading(heading)
298 .withLatitude(point.getLatitude())
299 .withLongitude(point.getLongitude());
300 }
301
302
303
304
305
306
307
308
309
310
311 private String getPoiDescription(POI poi) {
312 String description = StringEscapeUtils.escapeHtml4(poi.getDescription());
313 if (poi.getUrl() != null && poi.getUrl().length() > 0) {
314 description += "<br /><a href=\"" + poi.getUrl() + "\" target=\"_blank\">" + poi.getUrl() + "</a>";
315 }
316 return description;
317 }
318
319 private double computeFlyTime(Point p1, Point p2){
320 return Spherical.computeDistanceBetween(p1, p2) * 2.2369363 / FLY_SPEED;
321 }
322
323 private Map<Integer, List<Integer>> findPoiTourIndices(List<Point> path, List<POI> pois) {
324 Map<Integer, List<Integer>> markerPathIndices = new HashMap<Integer, List<Integer>>();
325 double shortestVal;
326 int shortestIndex;
327 Point poiLocation;
328 Point pathVertex;
329 double distance;
330 POI poi;
331
332 for (int m = 0; m < pois.size(); m++) {
333 poi = pois.get(m);
334 poiLocation = new Point(poi.getLatitude(), poi.getLongitude());
335 shortestVal = 999999;
336 shortestIndex = 0;
337 int index = 0;
338 for (Iterator<Point> iter = path.iterator(); iter.hasNext();) {
339 pathVertex = iter.next();
340 distance = Spherical.computeDistanceBetween(pathVertex, poiLocation);
341 if (distance < shortestVal) {
342 shortestVal = distance;
343 shortestIndex = index;
344 }
345 index++;
346 }
347 List<Integer> nearbyPois = markerPathIndices.get(shortestIndex);
348 if (nearbyPois == null) {
349 nearbyPois = new ArrayList<Integer>();
350 markerPathIndices.put(shortestIndex, nearbyPois);
351 }
352 nearbyPois.add(m);
353 }
354
355 for (Map.Entry<Integer, List<Integer>> entry : markerPathIndices.entrySet()) {
356 List<Integer> nearbyPois = entry.getValue();
357 if (nearbyPois.size() > 1) {
358 pathVertex = path.get(entry.getKey());
359 Map<Integer, Double> markerDistMap = new HashMap<Integer, Double>();
360 for (int poiIndex : nearbyPois){
361 poi = pois.get(poiIndex);
362 poiLocation = new Point(poi.getLatitude(), poi.getLongitude());
363 distance = Spherical.computeDistanceBetween(pathVertex, poiLocation);
364 markerDistMap.put(poiIndex, distance);
365 }
366 entry.setValue(sortByValue(markerDistMap));
367 }
368 }
369 return markerPathIndices;
370 }
371
372 private static List<Integer> sortByValue(final Map<Integer, Double> m) {
373 List<Integer> keys = new ArrayList<Integer>();
374 keys.addAll(m.keySet());
375 Collections.sort(keys, new Comparator<Integer>() {
376 public int compare(Integer o1, Integer o2) {
377 Double v1 = m.get(o1);
378 Double v2 = m.get(o2);
379 if (v1 == null) {
380 return (v2 == null) ? 0 : 1;
381 }
382 return v1.compareTo(v2);
383 }
384 });
385 return keys;
386 }
387
388
389 private static List<Point> decodePolyLine(String encoded) {
390 List<Point> poly = new ArrayList<Point>();
391 int index = 0, len = encoded.length();
392 int lat = 0, lng = 0;
393 while (index < len) {
394 int b, shift = 0, result = 0;
395 do {
396 b = encoded.charAt(index++) - 63;
397 result |= (b & 0x1f) << shift;
398 shift += 5;
399 } while (b >= 0x20);
400 int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
401 lat += dlat;
402 shift = 0;
403 result = 0;
404 do {
405 b = encoded.charAt(index++) - 63;
406 result |= (b & 0x1f) << shift;
407 shift += 5;
408 } while (b >= 0x20);
409 int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
410 lng += dlng;
411 org.kuali.mobility.math.geometry.Point p = new org.kuali.mobility.math.geometry.Point((((double) lat / 1E5)),
412 (((double) lng / 1E5)));
413 poly.add(p);
414 }
415 return poly;
416 }
417 }