Java tutorial
/** * (c) Copyright, Moebius Solutions, Inc., 2012 * * All Rights Reserved * * LICENSE: GPLv3 */ package com.moesol.gwt.maps.client; import java.util.Date; import com.google.gwt.event.shared.EventBus; import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.user.client.Cookies; import com.google.gwt.user.client.EventListener; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AbsolutePanel; import com.google.gwt.user.client.ui.ChangeListener; import com.google.gwt.user.client.ui.ChangeListenerCollection; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FocusPanel; import com.google.gwt.user.client.ui.SourcesChangeEvents; import com.moesol.gwt.maps.client.stats.Sample; import com.moesol.gwt.maps.client.units.AngleUnit; import com.moesol.gwt.maps.client.units.Degrees; import com.moesol.gwt.maps.client.units.MapScale; import com.moesol.gwt.maps.shared.BoundingBox; public class MapView extends Composite implements IMapView, SourcesChangeEvents { private static final long ONE_YEAR = 365 * 24 * 60 * 60 * 1000; private final AbsolutePanel m_viewPanel = new AbsolutePanel(); private final FocusPanel m_focusPanel = new FocusPanel(); private final MapController m_mapEventListener; private IProjection m_mapProj = null; private final ViewPort m_viewPort = new ViewPort(); private final DivManager m_divMgr = new DivManager(this); private final AnimationEngine m_animateEngine = new AnimationEngine(this); // private final IFlyTo m_flyToEngine = new FlyToEngine(this); TODO private final IFlyTo m_flyToEngine = new FlyToSimple(this); private final IconEngine m_iconEngine = new IconEngine(this); private DeclutterEngine m_declutterEngine; // null unless needed private final ChangeListenerCollection m_changeListeners = new ChangeListenerCollection(); private final IconLayer m_iconLayer = new IconLayer(); private int m_dpi = AbstractProjection.DOTS_PER_INCH; private boolean m_bSuspendMapAction = false; private boolean m_bDeclutterLabels = false; private double m_mapBrightness = 1.0; private boolean m_bProjSet = false; private double m_previousEqScale; private final EventBus m_eventBus; private final DynamicUpdateEngine m_dynamicUpdateEngine; public MapView() { this(new SimpleEventBus()); } public MapView(final EventBus eventBus) { this(new CylEquiDistProj(), eventBus); } public MapView(final IProjection projection) { this(projection, new SimpleEventBus()); } public MapView(final IProjection projection, final EventBus eventBus) { this(projection, eventBus, Degrees.geodetic(0, 0)); } public MapView(final IProjection projection, final double defLat, GeodeticCoords defaultCenter) { this(projection, new SimpleEventBus(), defaultCenter); } public MapView(final IProjection projection, final EventBus eventBus, GeodeticCoords defaultCenter) { setProjection(projection); m_eventBus = eventBus; m_mapEventListener = new MapController(this, m_eventBus); m_dynamicUpdateEngine = new DynamicUpdateEngine(this, m_eventBus); initialize(defaultCenter); } private void initialize(GeodeticCoords defaultCenter) { ProjectionValues.readCookies(m_mapProj); setCenter( recoverCenter(defaultCenter.getLambda(AngleUnit.DEGREES), defaultCenter.getPhi(AngleUnit.DEGREES))); updateSize(m_viewPort.getWidth(), m_viewPort.getHeight()); m_dynamicUpdateEngine.initDynamicRefreshTimer(); m_focusPanel.setStyleName("moesol-MapView"); m_focusPanel.getElement().setId("FocusPanelId"); m_mapEventListener.bindHandlers(m_focusPanel); ViewDimension v = m_viewPort.getVpWorker().getDimension(); m_viewPanel.setPixelSize(v.getWidth(), v.getHeight()); m_viewPanel.getElement().getStyle().setZIndex(2000); m_viewPanel.getElement().setId("ViewPanelId"); m_divMgr.setViewWorker(m_viewPort.getVpWorker()); m_divMgr.attachDivsTo(m_viewPanel); m_focusPanel.setWidget(m_viewPanel); //placeDivPanels(); initWidget(m_focusPanel); //doUpdateView(); } public AbsolutePanel getViewPanel() { return m_viewPanel; } public IconEngine getIconEngine() { return m_iconEngine; } public EventBus getEventBus() { return m_eventBus; } public DivManager getDivManager() { return m_divMgr; } public void setProjection(IProjection proj) { m_mapProj = proj; m_previousEqScale = proj.getEquatorialScale(); m_viewPort.setProjection(proj); } private boolean setProjFromLayerSet(LayerSet ls) { if (!ls.isActive()) { return false; } if (ls.isAlwaysDraw()) { return false; } //if (m_mapProj != null) { // if ( m_mapProj.doesSupport(ls.getSrs()) && m_bProjSet ) // return true; //} IProjection proj = Projection.getProj(ls); if (proj != null) { setProjection(proj); return true; } return false; } public void addEventListener(EventListener eventListener) { this.addEventListener(eventListener); } public void setSuspendFlag(boolean flag) { this.m_bSuspendMapAction = flag; } public boolean isMapActionSuspended() { return this.m_bSuspendMapAction; } /** * @return true when label declutter is enabled. */ public boolean isDeclutterLabels() { return m_bDeclutterLabels; } /** * Set declutter labels flag. When declutter labels is enabled the map view will * move the labels so that they do not overlap each other. * @param bDeclutterLabels */ public void setDeclutterLabels(boolean bDeclutterLabels) { m_bDeclutterLabels = bDeclutterLabels; } public DeclutterEngine getDeclutterEngine() { if (m_declutterEngine == null) { m_declutterEngine = new DeclutterEngine(this); } return m_declutterEngine; } public MapController getController() { return m_mapEventListener; } public ViewPort getViewport() { return m_viewPort; } public void setDpi(int dpi) { m_dpi = dpi; } public int getDpi() { return m_dpi; } private GeodeticCoords recoverCenter(double defLng, double defLat) { String centerLng = Cookies.getCookie("centerLng"); String centerLat = Cookies.getCookie("centerLat"); if (centerLng == null || centerLat == null) { return new GeodeticCoords(defLng, defLat, AngleUnit.DEGREES); } try { double lng = Double.parseDouble(centerLng); double lat = Double.parseDouble(centerLat); return new GeodeticCoords(lng, lat, AngleUnit.DEGREES); } catch (NumberFormatException e) { return new GeodeticCoords(defLng, defLat, AngleUnit.DEGREES); } } /** * Called when map changed and idle, when this method is called onIdle is not called. */ void onChangeAndIdle() { // Map went idle force suspend flag to off, work around bug in IE setSuspendFlag(false); // Things changed, save cookies recordCenter(); ProjectionValues.writeCookies(getProjection()); doDeclutterView(); } /** * Called when map goes idle. When this method is called onChangeAndIdle is not called. */ long m_oldIconVersion = 0L; void onIdle() { // Map went idle force suspend flag to off, work around bug in IE setSuspendFlag(false); if (!isDeclutterLabels()) { return; } if (m_oldIconVersion == getIconLayer().getVersion()) { return; } m_oldIconVersion = getIconLayer().getVersion(); doDeclutterView(); } private void doDeclutterView() { if (isDeclutterLabels()) { getDeclutterEngine().incrementalDeclutter(getIconLayer().getIcons(), m_iconEngine, m_divMgr.getCurrentDiv().getDivWorker()); } } private void recordCenter() { String centerLng = Double.toString(getCenter().getLambda(AngleUnit.DEGREES)); String centerLat = Double.toString(getCenter().getPhi(AngleUnit.DEGREES)); Date expire = new Date(new Date().getTime() + ONE_YEAR); Cookies.setCookie("centerLng", centerLng, expire); Cookies.setCookie("centerLat", centerLat, expire); } public void addLayersFromLayerConfig(ILayerConfigAsync config) { config.getLayerSets(new AsyncCallback<LayerSet[]>() { @Override public void onFailure(Throwable caught) { Window.alert("Failed to get layer config " + caught); } @Override public void onSuccess(LayerSet[] result) { LayerSet[] layers = result; for (int i = 0; i < layers.length; i++) { addLayer(layers[i]); } updateView(); } }); } public void addLayer(LayerSet layerSet) { if (m_bProjSet == false) { m_bProjSet = setProjFromLayerSet(layerSet); if (m_bProjSet) { m_divMgr.setProjFromLayerSet(layerSet); } } m_divMgr.addLayer(layerSet); return; } public void addLayers(LayerSet[] layers) { for (int i = 0; i < layers.length; i++) { addLayer(layers[i]); } } public void removeLayer(LayerSet layerSet) { m_divMgr.removeLayer(layerSet); } public void clearLayers() { m_divMgr.clearLayers(); } public IconLayer getIconLayer() { return m_iconLayer; } public IProjection getProjection() { return m_mapProj; } /** * Sets the map center * * @param latDegrees * (latitude in degrees) * @param lngDegrees * (longitudes in degrees) */ // TODO remove this method use setCenter(Degrees.geodetic(lat, lng)) instead public void setCenter(double latDegrees, double lngDegrees) { GeodeticCoords center = Degrees.geodetic(latDegrees, lngDegrees); setCenter(center); } /** * Center on a position and scale * * @param latDegrees * @param lngDegrees * @param scale */ // TODO remove this method does not properly encapsulate DEGREES/RADIANS public void setCenterScale(double latDegrees, double lngDegrees, double scale) { GeodeticCoords center = Degrees.geodetic(latDegrees, lngDegrees); if (scale > 0) { m_mapProj.setEquatorialScale(scale); } setCenter(center); } /** * Sets the map center * * @param center */ public void setCenter(GeodeticCoords center) { m_viewPort.getVpWorker().setGeoCenter(center); } public GeodeticCoords getCenter() { return m_viewPort.getVpWorker().getGeoCenter(); } public ViewCoords getViewCenter() { return m_viewPort.worldToView(getWorldCenter(), true); } public WorldCoords getWorldCenter() { return m_viewPort.getVpWorker().getVpCenterInWc(); } /** * This routine sets the viewport's center in WCs * Is does not force an update of the map tiles. * @param worldCenter */ public WorldCoords setWorldCenter(WorldCoords worldCenter) { WorldCoords wc = m_viewPort.constrainAsWorldCenter(worldCenter); m_viewPort.getVpWorker().setCenterInWc(wc); return wc; } private final Timer m_updateTimer = new Timer() { @Override public void run() { m_isUpdateTimerScheduled = false; dumbUpdateView(); } }; private boolean m_isUpdateTimerScheduled = false; private boolean m_resized = false; /** * Match the view to the model data. */ public void updateView() { if (m_declutterEngine != null) { m_declutterEngine.cancelIncrementalDeclutter(); } m_animateEngine.cancel(); if (m_isUpdateTimerScheduled) { return; } m_updateTimer.schedule(1000 / 30); m_isUpdateTimerScheduled = true; } public void declutterView() { doDeclutterView(); } public void cancelAnimations() { m_animateEngine.cancel(); m_flyToEngine.getAnimation().cancel(); } private boolean hasLevelChanged() { int prevLevel = m_mapProj.getLevelFromScale(m_previousEqScale, 0); double scale = m_mapProj.getEquatorialScale(); int currentLevel = m_mapProj.getLevelFromScale(scale, 0); m_previousEqScale = m_mapProj.getEquatorialScale(); return (prevLevel != currentLevel); } public void dumbUpdateView() { boolean updateWorkAround = true; if (updateWorkAround || m_resized || m_dynamicUpdateEngine.isDynamicUpdateNeeded() || hasLevelChanged() || m_divMgr.hasDivMovedToFar()) { m_resized = false; m_dynamicUpdateEngine.setDynamicUpdateNeeded(false); fullUpdateView(); } else { partialUpdateView(); } m_mapEventListener.fireMapViewChangeEventWithMinElapsedInterval(500); m_changeListeners.fireChange(this); // TODO move to idle handling... // recordCenter(); // ProjectionValues.writeCookies(m_proj); } public void fullUpdateView() { Sample.MAP_FULL_UPDATE.beginSample(); // Do Not change the order of the next two // methods are called in m_divMgr.doUpdateDivsVisibility(m_viewPanel); m_divMgr.doUpdateDivsCenterScale(m_mapProj.getEquatorialScale()); m_divMgr.placeDivsInViewPanel(m_viewPanel); m_divMgr.positionIcons(); Sample.MAP_FULL_UPDATE.endSample(); } public void partialUpdateView() { Sample.MAP_PARTIAL_UPDATE.beginSample(); m_divMgr.placeDivsInViewPanel(m_viewPanel); //m_divMgr.positionIcons(); Sample.MAP_PARTIAL_UPDATE.endSample(); } public boolean hasAutoRefreshOnTimerLayers() { return m_divMgr.hasAutoRefreshOnTimerLayers(); } @Override public String toString() { return getCenter().toString(); } public void setFocus(boolean bFocus) { m_focusPanel.setFocus(bFocus); } @Override public void setPixelSize(int width, int height) { super.setPixelSize(width, height); updateSize(width, height); } @Override public void setSize(String width, String height) { super.setSize(width, height); m_viewPanel.setSize(width, height); // below fails in IE //updateSize(getOffsetWidth(), getOffsetHeight()); } private void updateSize(int width, int height) { m_viewPort.setSize(width, height); //m_iconsOverTilesPanel.setPixelSize(width, height); m_viewPanel.setPixelSize(width, height); m_focusPanel.setPixelSize(width, height); } @Override public void addChangeListener(ChangeListener listener) { m_changeListeners.add(listener); } @Override public void removeChangeListener(ChangeListener listener) { m_changeListeners.remove(listener); } // ///////////////////////////////////////////////////////////////// // / User action calls ///////////////////////////////////////////// /** * Resize the map view to the width and height. Call this method when the * map view is resized so that the internal view engine know what size to * use. */ public void resizeMap(int w, int h) { m_resized = true; setPixelSize(w, h); m_divMgr.resizeDivs(w, h); WorldCoords wc = m_viewPort.getVpWorker().getVpCenterInWc(); setWorldCenter(wc); updateView(); } /** * Pan map by dx, dy; * * @param dx * @param dy * @return */ public void moveMapByPixels(int dx, int dy) { cancelAnimations(); ViewWorker vpWorker = this.getViewport().getVpWorker(); WorldCoords centerInWc = vpWorker.getVpCenterInWc(); setWorldCenter(centerInWc.translate(dx, dy)); updateView(); } public boolean zoomToNextLevelOnPixel(int x, int y, boolean bIn) { if (m_bSuspendMapAction == false) { int level = m_divMgr.getCurrentLevel() + (bIn ? 1 : -1); level = Math.max(Math.min(level, DivManager.NUMDIVS - 1), 0); double currentScale = m_mapProj.getEquatorialScale(); double scale = m_mapProj.getScaleFromLevel(level); double factor = scale / currentScale; m_animateEngine.animateZoomMap(x, y, factor); } return (m_bSuspendMapAction == false); } /** * Animate zoom map, but keep the latitude and longitude under x, y * during the zoom and after its completion. * * @param x * @param y * @param bZoomIn * @return */ public boolean zoomOnPixel(int x, int y, double scaleFactor) { if (m_bSuspendMapAction == false) { double scale = m_mapProj.getEquatorialScale(); int level = m_mapProj.getLevelFromScale(scale * scaleFactor, 0.05); if (level < DivManager.NUMDIVS) { m_animateEngine.animateZoomMap(x, y, scaleFactor); } } return (m_bSuspendMapAction == false); } /** * Non-animated zoom in. */ public void zoomByFactor(double factor) { m_mapProj.zoomByFactor(factor); m_viewPort.getVpWorker().updateOffsets(); dumbUpdateView(); } public void zoom(double dScale) { m_mapProj.setEquatorialScale(dScale); updateView(); } public void animateZoomToNextLevel(boolean bIn) { ViewDimension v = m_viewPort.getVpWorker().getDimension(); int x = v.getWidth() / 2; int y = v.getHeight() / 2; zoomToNextLevelOnPixel(x, y, bIn); } public void animateZoom(double scaleFactor) { ViewDimension v = m_viewPort.getVpWorker().getDimension(); int x = v.getWidth() / 2; int y = v.getHeight() / 2; zoomOnPixel(x, y, scaleFactor); } /** * Fly to center/scale. * @param center * @param scale */ public void flyTo(GeodeticCoords center, MapScale scale) { m_flyToEngine.flyTo(center, scale.asDouble()); } /** * Clients will call this routine to fly to a center lat, lng and scale * based on a bounding box. * * @param box */ public void flyTo(BoundingBox box) { GeodeticCoords gc = new GeodeticCoords(box.getCenterLng(), box.getCenterLat(), AngleUnit.DEGREES); m_flyToEngine.flyTo(gc, Projections.findScaleFor(getViewport(), box)); } public int getDynamicRefreshMillis() { return m_dynamicUpdateEngine.getDynamicRefreshMillis(); } public void setDynamicRefreshMillis(int dynamicRefreshMillis) { m_dynamicUpdateEngine.setDynamicRefreshMillis(dynamicRefreshMillis); } public long getDynamicCounter() { return m_dynamicUpdateEngine.getDynamicCounter(); } public void setMapBrightness(double val) { this.m_mapBrightness = Math.min(1.0, Math.max(0.0, val)); } public double getMapBrightness() { return m_mapBrightness; } public void incrementMapBrightness(double val) { if ((m_mapBrightness == 1.0 && val > 0) || (m_mapBrightness == 0.0 && val < 0)) { return; } m_mapBrightness += val; setMapBrightness(m_mapBrightness); m_divMgr.setOpacity(getMapBrightness()); } public void setAnimationDurationSeconds(int v) { m_animateEngine.setDurationInSecs(v); } public int getAnimationDurationSeconds() { return m_animateEngine.getDurationInSecs(); } public FocusPanel getFocusPanel() { return m_focusPanel; } @Override public IProjection getTempProjection() { return m_mapProj.cloneProj(); } public WidgetPositioner getWidgetPositioner() { return m_divMgr.getWidgetPositioner(); } // private boolean iconsHaveMoved() { // return m_iconLayer.iconsHaveMoved(); // } }