package votorola.s.gwt.scene.geo; // Copyright 2011-2012, Michael Allan. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE. import com.google.gwt.core.client.*; import com.google.web.bindery.event.shared.HandlerRegistration; import com.google.gwt.user.client.Cookies; import com.google.gwt.view.client.SelectionChangeEvent; import java.util.*; import org.gwtopenmaps.openlayers.client.*; import org.gwtopenmaps.openlayers.client.control.*; import org.gwtopenmaps.openlayers.client.event.*; import org.gwtopenmaps.openlayers.client.feature.*; import org.gwtopenmaps.openlayers.client.geometry.*; import org.gwtopenmaps.openlayers.client.layer.*; import org.gwtopenmaps.openlayers.client.layer.Vector; // over java.util.Vector import org.gwtopenmaps.openlayers.client.util.JSObject; import votorola.a.count.*; import votorola.a.web.gwt.*; import votorola.s.gwt.scene.*; import votorola.s.gwt.scene.feed.*; import votorola.s.gwt.stage.*; import votorola.g.hold.*; import votorola.g.lang.*; /** A GWT widget wrapping an OpenLayers geographic map and its views. Apparently * OpenLayers does not allow for separate views of the same map to be placed here and * there; all must be stacked (layered) together under a single DOM object. This is the * widget that wraps that DOM object. It uses {@linkplain Geoscoping geoscoping}, * q.v. for the format of the 's' scoping switch. * * @see Live example of a Geomap * (right) */ public final class Geomap extends MapWidget implements Scene { /** Does nothing itself but the call forces static initialization of this class. */ public static void forceInitClass() {} /** Constructs a Geomap. * * @param _spool the spool for the release of associated holds. When unwound it * releases the holds of the geomap and thereby disables it. */ public Geomap( Spool _spool ) { super( /*width*/"100%", /*height*/"100%", newMapOptions() ); // options must be passed into constructor, or nothing is displayed (GWT-OpenLayers 0.5) this.spool = _spool; final org.gwtopenmaps.openlayers.client.Map map = getMap(); spool.add( new Hold() { public void release() { try{ map.destroy(); } catch( final JavaScriptException x ) { if( !GWT.isProdMode() && "NOT_FOUND_ERR".equals( x.getName() ) ) { System.out.println( "suppressing known error (devmode under Chrome) during map.destroy(): " + x ); return; } throw x; } } }); // map.addControl( new MousePosition() ); // Layers // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { final VectorOptions options = new VectorOptions(); options.setStyle( newPersonStyle() ); peopleVector = new Vector( "People", options ); } map.addLayer( peopleVector ); { init_baseLayer( map, OSM.CycleMap( "Cycle Map OSM" )); final Layer defaultLayer; { final GoogleOptionsX go = new GoogleOptionsX(); go.setType( googleMapTypeId_TERRAIN() ); init_baseLayer( map, new Google( "Google Physical", go )); init_baseLayer( map, new Google( "Google Roads" )); go.setType( googleMapTypeId_HYBRID() ); init_baseLayer( map, new Google( "Google Hybrid", go )); go.setType( googleMapTypeId_SATELLITE() ); init_baseLayer( map, defaultLayer = new Google( "Google Satellite", go )); } init_baseLayer( map, OSM.Mapnik( "Mapnik OSM" )); // init_baseLayer( map, OSM.Osmarender( "Osmarender OSM" )); ///// JavaScriptException: "$wnd.OpenLayers.Layer.OSM.Osmarender is not a constructor", 2012.3 // { // final WMSParams p = new WMSParams(); // p.setLayers( "basic" ); // init_baseLayer( map, new WMS( "VMAP OSGeo WMS", // "http://vmap0.tiles.osgeo.org/wms/vmap0", p )); // } ///// WMS doesn't use the spherical mercator projection, at least not by default Layer initialLayer = null; { new BaseLayerPersister(); final String cookie = Cookies.getCookie( BASE_LAYER_COOKIE ); if( cookie != null ) initialLayer = map.getLayerByName( cookie ); if( initialLayer == null ) { initialLayer = map.getLayerByName( getDefaultBaseLayerName() ); } if( initialLayer == null ) initialLayer = defaultLayer; } map.setBaseLayer( initialLayer ); } map.addControl( new LayerSwitcher() ); // - - - new BiteLighter(); scoping = new Geoscoping( Geomap.this, spool ); // Zooms and centers. Must at least zoom or map invisible. Must zoom late, or // it has no effect. } private static void init_baseLayer( final org.gwtopenmaps.openlayers.client.Map map, final Layer layer ) { // layer.setIsBaseLayer( true ); /// that's usually the default map.addLayer( layer ); } // ------------------------------------------------------------------------------------ /** The default base layer for the geomap. The layer is specified by its name in the * layer switcher. The state of the switcher is persisted through a cookie, so this * value is only a default. If no value is set, then "Google Satellite" is used. * * @see #setDefaultBaseLayerName(String) */ public static @GWTConfigCallback String getDefaultBaseLayerName() { return defaultBaseLayerName; } private static String defaultBaseLayerName = "Google Satellite"; // because it looks best private static native void exposeDefaultBaseLayerName() /*-{ $wnd.s_gwt_scene_geo_Geomap_getDefaultBaseLayerName = $entry( @votorola.s.gwt.scene.geo.Geomap::getDefaultBaseLayerName() ); $wnd.s_gwt_scene_geo_Geomap_setDefaultBaseLayerName = $entry( @votorola.s.gwt.scene.geo.Geomap::setDefaultBaseLayerName(Ljava/lang/String;) ); }-*/; static { assert SceneIn.isForcedInit(): "forced init " + Geomap.class.getName(); exposeDefaultBaseLayerName(); } /** Sets the default base layer for the geomap. Call this method from the global * configuration function {@linkplain SceneIn voGWTConfig.s_gwt_scene} in this * manner:
   s_gwt_scene_geo_Geomap_setDefaultBaseLayerName( 'Osmarender OSM' );
          *     // otherwise 'Google Satellite'
* * @throws NullPointerException if s is null. * @see #getDefaultBaseLayerName() */ public static @GWTConfigCallback void setDefaultBaseLayerName( final String s ) { if( s == null ) throw new NullPointerException(); // fail fast defaultBaseLayerName = s; } /** Transforms from latitude/longitude coordinates (EPSG:4326) to the projection of * this geomap, which is spherical mercator (EPSG:900913). */ void transformFromEPSG4326( final Point point ) // because GWT-OpenLayers lacks Point.transform() { // Cf. forwardMercator() in OpenLayers.Layer.SphericalMercator and // org.gwtopenmaps.openlayers.client.layer.Google, which works with LonLat. transform( point.getJSObject(), EPSG4326.getJSObject(), getMap().getJSObject() ); } // - M a p - ( votorola.s.gwt.scene ) ------------------------------------------------- public boolean inScope( BiteJS bite ) { return scoping.inScope( bite ); } //// P r i v a t e /////////////////////////////////////////////////////////////////////// private static final Projection EPSG4326 = new Projection( "EPSG:4326" ); // WGS84 with latitude/longitude coordinates private static native String googleMapTypeId_HYBRID() /*-{ return $wnd.google.maps.MapTypeId.HYBRID; }-*/; private static native String googleMapTypeId_ROADMAP() /*-{ return $wnd.google.maps.MapTypeId.ROADMAP; }-*/; private static native String googleMapTypeId_SATELLITE() /*-{ return $wnd.google.maps.MapTypeId.SATELLITE; }-*/; private static native String googleMapTypeId_TERRAIN() /*-{ return $wnd.google.maps.MapTypeId.TERRAIN; }-*/; private static MapOptions newMapOptions() { final MapOptions o = new MapOptions(); o.setProjection( "EPSG:900913" ); // incoming map data (spherical mercator) // Expected as a string not a Projection, for some reason. You get the // Projection as map.getProjectionObject() (not yet coded in GWT), but only // after adding a base layer. o.setUnits( "m" ); // spherical mercator uses meters o.setDisplayProjection( EPSG4326 ); // outgoing meta-information for users // o.setNumZoomLevels( 18 ); // o.setMaxResolution( 156543.0339f ); // o.setMaxExtent( new Bounds( -20037508.34, -20037508.34, 20037508.34, 20037508.344 )); /// only needed if "using a standalone WMS or TMS layer" // http://docs.openlayers.org/library/spherical_mercator.html return o; } private static Style newPersonStyle() { final Style s = new Style(); // apparently cannot set CSS class, so must use this API // s.setFillColor( "#CC4444" ); // s.setFillOpacity( 0.2f ); s.setFillOpacity( 0 ); s.setPointRadius( 12/*pixels*/ ); s.setStrokeColor( "#FF0000" ); s.setStrokeOpacity( 0.7f ); s.setStrokeWidth( 2/*pixels*/ ); return s; } private final Vector peopleVector; private final Geoscoping scoping; private final Spool spool; private static native void transform( JSObject object, JSObject source, JSObject map ) /*-{ object.transform( source, map.getProjectionObject() ); }-*/; // ==================================================================================== private static final String BASE_LAYER_COOKIE = "voGeomap.initialBaseLayer"; private final class BaseLayerPersister { /** Constructs a BaseLayerPersister. */ BaseLayerPersister() { // Registration key in GWT-OpenLayers is listener itself, so we must implement // a separate listener for each event type. final MapBaseLayerChangedListener l = new MapBaseLayerChangedListener() { public void onBaseLayerChanged( final MapBaseLayerChangedListener.MapBaseLayerChangedEvent e ) { final Layer layer = e.getLayer(); if( layer == null ) return; final Date expiryDate = new Date( System.currentTimeMillis() + 1000L/*ms per s*/ * 3600/*s per hour*/ * 24/*hours per day*/ * 30/*days per month*/ * 6/*months*/ ); assert Cookies.getUriEncode(): "cookies automatically URI encoded"; Cookies.setCookie( BASE_LAYER_COOKIE, layer.getName(), expiryDate ); } }; getMap().addMapBaseLayerChangedListener( l ); spool.add( new Hold() { public void release() { getMap().removeListener( l ); } }); } } // ==================================================================================== private final class BiteLighter implements SelectionChangeEvent.Handler, TheatreInitializer { BiteLighter() { Stage.i().addInitializer( BiteLighter.this ); } // auto-removed private void init() { if( spool.isUnwinding() ) return; spool.add( new Hold() { final HandlerRegistration hR = Scenes.i().biteSelection().addSelectionChangeHandler( BiteLighter.this ); public void release() { hR.removeHandler(); } }); relight(); // init state } // -------------------------------------------------------------------------------- private void clear( final Stage stage ) { Stage.setActorName( null ); stage.setPollName( null ); } private final Style firstPersonStyle = newPersonStyle(); { firstPersonStyle.setStrokeOpacity( 1 ); // emphasized firstPersonStyle.setStrokeWidth( 3/*pixels*/ ); } private void relight() { final BiteJS bite = Scenes.i().biteSelection().getSelectedObject(); // if( SAME GEOLOCS AS LAST TIME )) return; // OPT if it's ever needed peopleVector.destroyFeatures(); final Stage stage = Stage.i(); if( bite == null ) { clear( stage ); return; } final Poll poll = bite.poll(); if( poll != null ) stage.setPollName( poll.name() ); final JsArray persons = bite.persons(); for( int p = 0, pN = persons.length(); p < pN; ++p ) { final Person person = persons.get( p ); if( p == 0 ) Stage.setActorName( person.username() ); // put first person on stage final Residence res = person.residence(); if( res == null ) continue; final Point point = new Point( res.lon(), res.lat() ); transformFromEPSG4326( point ); final VectorFeature feature = new VectorFeature( point ); if( p == 0 ) feature.setStyle( firstPersonStyle ); peopleVector.addFeature( feature ); } } // - S e l e c t i o n - C h a n g e - E v e n t . H a n d l e r ------------------ public void onSelectionChange( SelectionChangeEvent _e ) { relight(); } // - T h e a t r e - I n i t i a l i z e r ---------------------------------------- public void initFrom( Stage _s, boolean _isReferencePending ) { init(); } // Does the reverse (init to) for the same reason documented for // s.gwt.scene.BiteStager.initFrom. public void initFromComplete( Stage _stage, boolean _isReferencePending ) {} public void initTo( Stage _s ) { init(); } public void initTo( Stage _s, TheatrePage _referrer ) { init(); } public void initToComplete( Stage _s, boolean _isReferencePending ) {} public void initUltimately( Stage _s, TheatrePage _referrer ) {} } // ==================================================================================== private static final class GoogleOptionsX extends GoogleOptions { public void setType( final String type ) { getJSObject().setProperty( "type", type ); } } }