View Javadoc

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;//mph
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 //		if (user.isMember(configParamService.findValueByName("Admin.Group.Name"))) {
144 //			return true;
145 //		} else {
146 //			if (tour.getEditGroups().isEmpty()) {
147 //				return true;
148 //			} else {
149 //				for (String group : tour.getEditGroups()){
150 //					if (user.isMember(group)) {
151 //						return true;
152 //					}
153 //				}
154 //			}
155 //		}
156 //		
157 //		return false;
158 		return true;
159 	}
160 	
161 	@Override
162 	public boolean hasAccessToViewTour(User user, Tour tour) {
163 //		if (user.isMember(configParamService.findValueByName("Admin.Group.Name"))) {
164 //			return true;
165 //		} else {
166 //			if (tour.getViewGroups().isEmpty()){
167 //				return true;
168 //			} else {
169 //				for (String group : tour.getViewGroups()){
170 //					if (user.isMember(group)) {
171 //						return true;
172 //					}
173 //				}
174 //			}
175 //		}
176 //		
177 //		return false;
178 		return true;
179 	}
180 	
181 	@Override
182 	public boolean hasAccessToEditPOI(User user, POI poi) {
183 //		if (user.isMember(configParamService.findValueByName("Admin.Group.Name"))) {
184 //			return true;
185 //		} else {
186 //			for (String group : poi.getEditGroups()){
187 //				if (user.isMember(group)) {
188 //					return true;
189 //				}
190 //			}
191 //		}
192 //		
193 //		return false;
194 		return true;
195 	}
196 
197 	@Override
198 	public boolean hasAccessToViewPOI(User user, POI poi) {
199 //		if (user.isMember(configParamService.findValueByName("Admin.Group.Name"))) {
200 //			return true;
201 //		} else {
202 //			if (poi.getViewGroups().isEmpty()){
203 //				return true;
204 //			} else {
205 //				for (String group : poi.getViewGroups()){
206 //					if (user.isMember(group)) {
207 //						return true;
208 //					}
209 //				}
210 //			}
211 //		}
212 //		
213 //		return false;
214 		return true;
215 	}
216 	
217 	@Override
218 	public boolean hasAccessToPublish(User user) {
219 //		if (user.isMember(configParamService.findValueByName("Admin.Group.Name")) || user.isMember(configParamService.findValueByName("Tours.Publishers.Group"))) {
220 //			return true;
221 //		} else {
222 //			return false;
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 //	        		buildShowHideBalloon(playlist, "poi" + poiIndex, true);
279 	        		playlist.createAndAddWait();
280 //	        		buildShowHideBalloon(playlist, "poi" + poiIndex, false);
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 //	private void buildShowHideBalloon(Playlist playlist, String placemarkId, boolean show) {
303 //		//new BalloonVisibility(BalloonVisibility.Visibility.SHOW)
304 //		List<Object> createOrDeleteOrChange = new ArrayList<Object>();
305 //		Change change = new Change().addToAbstractObject(new Placemark().withTargetId(placemarkId).addToFeatureObjectExtension(new BalloonVisibility(BalloonVisibility.Visibility.SHOW)));
306 //		createOrDeleteOrChange.add(change);
307 //		playlist.createAndAddAnimatedUpdate().createAndSetUpdate(null, createOrDeleteOrChange);
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; //convert to meters/second and return seconds
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>>(); //markerPathIndices[indexOfPointInPath] = poiMarkerIndexArray;  //will be null if the point is not the closest to a POI
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) { //no need to sort one item
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 	//http://www.geekyblogger.com/2010/12/decoding-polylines-from-google-maps.html
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 }