001: /*
002: *
003: * Copyright (c) 2007, Sun Microsystems, Inc.
004: *
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * * Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: * * Neither the name of Sun Microsystems nor the names of its contributors
017: * may be used to endorse or promote products derived from this software
018: * without specific prior written permission.
019: *
020: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
021: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
022: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
023: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
024: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
025: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
026: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
027: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
028: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
029: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
030: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
031: */
032: package examples.cityguide;
033:
034: import java.io.IOException;
035:
036: import java.util.Enumeration;
037: import java.util.Hashtable;
038: import java.util.Vector;
039:
040: import javax.microedition.lcdui.Image;
041: import javax.microedition.location.*;
042:
043: /**
044: * This class represents a map with a visitor and landmarks. It allows
045: * registration of a map listener which gets information about position changes
046: * of the visitor, activations and de-activations of the landmarks and changes
047: * of the whole landmark set. A class instance registers itself as a location
048: * listener and registers each landmark as a proximity listener, so it is
049: * notified about position change and proximity events from a location provider.
050: * After getting a notification from the location provider it updates its
051: * internal state and delegates the notification to its own listeners.
052: *
053: * @version 1.3
054: */
055: public class CityMap implements LocationListener {
056: public static int X = 0;
057: public static int Y = 1;
058: public static int IMAGE_MAP = 0;
059: public static int IMAGE_VISITOR_ON = 1;
060: public static int IMAGE_VISITOR_OFF = 2;
061: public static int IMAGE_LAST = IMAGE_VISITOR_OFF;
062: private static final int ACTIVATION_RADIUS = 50;
063: private static final int DEACTIVATION_RADIUS = 100;
064: private Coordinates topLeftCoordinates;
065: private Coordinates bottomRightCoordinates;
066: private ImageManager imageManager;
067: private LandmarkStore landmarkStore;
068: private LocationProvider locationProvider;
069: private Image[] images;
070: private Coordinates visitorCoordinates;
071: private int[] visitorXY;
072: private boolean visitorActive;
073: private MyMapLandmark[] landmarks;
074: private Vector mapListeners;
075: private boolean disabled;
076:
077: /** Creates a new instance of CityMap */
078: public CityMap(String[] imageNames, Coordinates topLeftCoordinates,
079: Coordinates bottomRightCoordinates,
080: Coordinates visitorCoordinates, Vector categories,
081: ImageManager imageManager, LandmarkStore landmarkStore,
082: LocationProvider locationProvider) {
083: this .topLeftCoordinates = topLeftCoordinates;
084: this .bottomRightCoordinates = bottomRightCoordinates;
085: this .imageManager = imageManager;
086: this .landmarkStore = landmarkStore;
087: this .locationProvider = locationProvider;
088:
089: this .visitorCoordinates = new Coordinates(0, 0, 0);
090: this .visitorXY = new int[2];
091: this .visitorActive = (locationProvider.getState() == LocationProvider.AVAILABLE);
092:
093: this .mapListeners = new Vector();
094:
095: images = new Image[IMAGE_LAST + 1];
096:
097: for (int i = 0; i <= IMAGE_LAST; ++i) {
098: images[i] = imageManager.getImage(imageNames[i]);
099: }
100:
101: setCategories(categories);
102: setVisitorCoordinates(visitorCoordinates);
103:
104: locationProvider.setLocationListener(this , -1, -1, -1);
105: }
106:
107: /**
108: * Changes the landmark set to contain only landmarks of the given
109: * categories.
110: */
111: public void setCategories(Vector categories) {
112: Hashtable tmpLandmarks = new Hashtable();
113:
114: Enumeration enumCategories = categories.elements();
115:
116: while (enumCategories.hasMoreElements()) {
117: String category = (String) enumCategories.nextElement();
118: Enumeration categoryLandmarks = null;
119:
120: try {
121: // get the landmarks of the given category from the landmark
122: // store
123: categoryLandmarks = landmarkStore.getLandmarks(
124: category, null);
125: } catch (IOException e) {
126: }
127:
128: if (categoryLandmarks != null) {
129: while (categoryLandmarks.hasMoreElements()) {
130: Landmark landmark = (Landmark) categoryLandmarks
131: .nextElement();
132:
133: if (!tmpLandmarks.containsKey(landmark)) {
134: // set the image of a landmark according to the first
135: // category the landmark belongs to
136: tmpLandmarks.put(landmark, imageManager
137: .getImage(category));
138: }
139: }
140: }
141: }
142:
143: // unregister the old landmarks from the location provider
144: synchronized (this ) {
145: if (landmarks != null) {
146: for (int i = 0; i < landmarks.length; ++i) {
147: locationProvider
148: .removeProximityListener(landmarks[i]);
149: }
150: }
151: }
152:
153: MyMapLandmark[] landmarks = new MyMapLandmark[tmpLandmarks
154: .size()];
155: Enumeration enumKeys = tmpLandmarks.keys();
156: Enumeration enumElements = tmpLandmarks.elements();
157: int j = 0;
158: int[] xy = new int[2];
159:
160: while (enumKeys.hasMoreElements()) {
161: Landmark landmark = (Landmark) enumKeys.nextElement();
162: Image image = (Image) enumElements.nextElement();
163: // calculate the xy coordinates of a landmark
164: convertCoordinatesToXY(xy, landmark
165: .getQualifiedCoordinates());
166: landmarks[j] = new MyMapLandmark(landmark, xy[X], xy[Y],
167: image);
168: ++j;
169: }
170:
171: // update the set of landmarks and notify the listeners about it
172: setLandmarks(landmarks);
173:
174: // register the new landmarks as proximity listeners
175: synchronized (this ) {
176: for (int i = 0; i < landmarks.length; ++i) {
177: try {
178: locationProvider.addProximityListener(landmarks[i],
179: landmarks[i].getLandmark()
180: .getQualifiedCoordinates(),
181: ACTIVATION_RADIUS);
182: } catch (LocationException e) {
183: }
184: }
185: }
186: }
187:
188: /** Updates the set of map landmarks and notifies the listeners about it. */
189: private synchronized void setLandmarks(MyMapLandmark[] mapLandmarks) {
190: landmarks = mapLandmarks;
191:
192: synchronized (mapListeners) {
193: Enumeration listeners = mapListeners.elements();
194:
195: while (listeners.hasMoreElements()) {
196: ((MapListener) listeners.nextElement())
197: .landmarksChanged(this );
198: }
199: }
200: }
201:
202: /**
203: * Activates / deactivates the given landmark and notifies listeners about
204: * it.
205: */
206: private synchronized void activateLandmark(MyMapLandmark landmark,
207: boolean activate) {
208: if (disabled) {
209: return;
210: }
211:
212: if (landmark.isActive() != activate) {
213: landmark.setActive(activate);
214:
215: synchronized (mapListeners) {
216: Enumeration listeners = mapListeners.elements();
217:
218: while (listeners.hasMoreElements()) {
219: ((MapListener) listeners.nextElement())
220: .landmarkStateChanged(this , landmark);
221: }
222: }
223: }
224: }
225:
226: /**
227: * Changes the coordinates of the visitor and notifies the listeners about
228: * it.
229: */
230: public synchronized void setVisitorCoordinates(
231: Coordinates newCoordinates) {
232: if ((visitorCoordinates.getLatitude() != newCoordinates
233: .getLatitude())
234: || (visitorCoordinates.getLongitude() != newCoordinates
235: .getLongitude())) {
236: visitorCoordinates
237: .setLatitude(newCoordinates.getLatitude());
238: visitorCoordinates.setLongitude(newCoordinates
239: .getLongitude());
240: convertCoordinatesToXY(visitorXY, visitorCoordinates);
241:
242: synchronized (mapListeners) {
243: Enumeration listeners = mapListeners.elements();
244:
245: while (listeners.hasMoreElements()) {
246: ((MapListener) listeners.nextElement())
247: .visitorPositionChanged(this );
248: }
249: }
250:
251: // deactivate active landmarks, which are now too away from the
252: // visitor
253: for (int i = 0; i < landmarks.length; ++i) {
254: if (landmarks[i].isActive()
255: && (newCoordinates.distance(landmarks[i]
256: .getLandmark()
257: .getQualifiedCoordinates()) > DEACTIVATION_RADIUS)) {
258: activateLandmark(landmarks[i], false);
259:
260: try {
261: // re-register a deactivated landmark to the location
262: // provider, so we can get notified again
263: locationProvider.addProximityListener(
264: landmarks[i], landmarks[i]
265: .getLandmark()
266: .getQualifiedCoordinates(),
267: ACTIVATION_RADIUS);
268: } catch (LocationException e) {
269: }
270: }
271: }
272: }
273: }
274:
275: /**
276: * Changes the state of the visitor. A deactivated visitor doesn't change
277: * his position.
278: */
279: public synchronized void setVisitorActive(boolean active) {
280: if (visitorActive != active) {
281: visitorActive = active;
282:
283: synchronized (mapListeners) {
284: Enumeration listeners = mapListeners.elements();
285:
286: while (listeners.hasMoreElements()) {
287: ((MapListener) listeners.nextElement())
288: .visitorStateChanged(this );
289: }
290: }
291: }
292: }
293:
294: /** Returns the xy coordinates of the visitor. */
295: public synchronized int[] getVisitorXY(int[] dest) {
296: if (dest == null) {
297: dest = new int[2];
298: }
299:
300: dest[X] = visitorXY[X];
301: dest[Y] = visitorXY[Y];
302:
303: return dest;
304: }
305:
306: /** Returns the visitor icon based on his state. */
307: public Image getVisitorImage() {
308: return images[visitorActive ? IMAGE_VISITOR_ON
309: : IMAGE_VISITOR_OFF];
310: }
311:
312: /** Returns the map image. */
313: public Image getMapImage() {
314: return images[IMAGE_MAP];
315: }
316:
317: /** Returns the set of the map landmarks. */
318: public MapLandmark[] getMapLandmarks() {
319: return landmarks;
320: }
321:
322: /** Registers a map listener. */
323: void addMapListener(MapListener listener) {
324: synchronized (mapListeners) {
325: mapListeners.addElement(listener);
326: }
327: }
328:
329: /** Unregisters a map listener. */
330: void removeMapListener(MapListener listener) {
331: synchronized (mapListeners) {
332: mapListeners.removeElement(listener);
333: }
334: }
335:
336: /**
337: * Converts from the given latitude / longitude coordinates to the map
338: * xy coordinates.
339: */
340: public int[] convertCoordinatesToXY(int[] dest, Coordinates coords) {
341: if (dest == null) {
342: dest = new int[2];
343: }
344:
345: double leftLatitude = topLeftCoordinates.getLatitude();
346: double rightLatitude = bottomRightCoordinates.getLatitude();
347: double topLongitude = topLeftCoordinates.getLongitude();
348: double bottomLongitude = bottomRightCoordinates.getLongitude();
349:
350: double normalizedX = (coords.getLatitude() - leftLatitude)
351: / (rightLatitude - leftLatitude);
352: double normalizedY = (coords.getLongitude() - topLongitude)
353: / (bottomLongitude - topLongitude);
354:
355: dest[X] = (int) (normalizedX * images[IMAGE_MAP].getWidth());
356: dest[Y] = (int) (normalizedY * images[IMAGE_MAP].getHeight());
357:
358: return dest;
359: }
360:
361: /**
362: * Converts from the given map xy coordinates to the latitude / longitude
363: * coordinates.
364: */
365: public Coordinates convertXYToCoordinates(Coordinates dest, int[] xy) {
366: double latitude = topLeftCoordinates.getLatitude()
367: + (((bottomRightCoordinates.getLatitude() - topLeftCoordinates
368: .getLatitude()) * xy[X]) / images[IMAGE_MAP]
369: .getWidth());
370: double longitude = topLeftCoordinates.getLongitude()
371: + (((bottomRightCoordinates.getLongitude() - topLeftCoordinates
372: .getLongitude()) * xy[Y]) / images[IMAGE_MAP]
373: .getHeight());
374: float altitude = topLeftCoordinates.getAltitude();
375:
376: if (dest == null) {
377: dest = new Coordinates(latitude, longitude, altitude);
378: } else {
379: dest.setLatitude(latitude);
380: dest.setLongitude(longitude);
381: dest.setAltitude(altitude);
382: }
383:
384: return dest;
385: }
386:
387: /**
388: * A method which is called by the location provider when the current
389: * location is changed.
390: */
391: public synchronized void locationUpdated(LocationProvider provider,
392: Location location) {
393: if (disabled) {
394: return;
395: }
396:
397: Coordinates coordinates = location.getQualifiedCoordinates();
398: double latitude = coordinates.getLatitude();
399: double longitude = coordinates.getLongitude();
400: double lat0 = topLeftCoordinates.getLatitude();
401: double lat1 = bottomRightCoordinates.getLatitude();
402: double lon0 = topLeftCoordinates.getLongitude();
403: double lon1 = bottomRightCoordinates.getLongitude();
404:
405: if ((((latitude >= lat0) && (latitude <= lat1)) || ((latitude >= lat1) && (latitude <= lat0)))
406: && (((longitude >= lon0) && (longitude <= lon1)) || ((longitude >= lon1) && (longitude <= lon0)))) {
407: setVisitorCoordinates(coordinates);
408: }
409: }
410:
411: /**
412: * A method which is called by the location provider when its state changes
413: * (for example, when its services are temporary unavailable).
414: */
415: public synchronized void providerStateChanged(
416: LocationProvider provider, int newState) {
417: if (disabled) {
418: return;
419: }
420:
421: setVisitorActive(newState == LocationProvider.AVAILABLE);
422: }
423:
424: /**
425: * Sets the city map to the disabled state. In the disabled state it ignores
426: * all notifications from the location provider.
427: */
428: public synchronized void disable() {
429: disabled = true;
430: }
431:
432: /**
433: * Sets the city map to the enabled state.
434: */
435: public synchronized void enable() {
436: disabled = false;
437: }
438:
439: /** The final unregistration. */
440: public synchronized void cleanup() {
441: for (int i = 0; i < landmarks.length; ++i) {
442: locationProvider.removeProximityListener(landmarks[i]);
443: }
444:
445: locationProvider.setLocationListener(null, -1, -1, -1);
446: }
447:
448: /**
449: * This class extends the MapLandmark class to support getting of proximity
450: * events from a location provider. It ignores the monitoring state changed
451: * events and delegates the proximity events to the CityMap instance.
452: */
453: private class MyMapLandmark extends MapLandmark implements
454: ProximityListener {
455: private int index;
456:
457: public MyMapLandmark(Landmark landmark, int x, int y,
458: Image image) {
459: super (landmark, x, y, image);
460: }
461:
462: public void setActive(boolean active) {
463: this .active = active;
464: }
465:
466: public void monitoringStateChanged(boolean isMonitoringActive) {
467: }
468:
469: public void proximityEvent(Coordinates coordinates,
470: Location location) {
471: activateLandmark(this , true);
472: }
473: }
474: }
|