org.wicketstuff.openlayers.OpenLayersMap.java Source code

Java tutorial

Introduction

Here is the source code for org.wicketstuff.openlayers.OpenLayersMap.java

Source

/*
 *
 * ==============================================================================
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package org.wicketstuff.openlayers;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.RuntimeConfigurationType;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.behavior.Behavior;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.request.Request;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.resource.JavaScriptResourceReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.wicketstuff.openlayers.api.Bounds;
import org.wicketstuff.openlayers.api.IJavascriptComponent;
import org.wicketstuff.openlayers.api.InfoWindow;
import org.wicketstuff.openlayers.api.LonLat;
import org.wicketstuff.openlayers.api.SphericalMercatorLonLat;
import org.wicketstuff.openlayers.api.Marker;
import org.wicketstuff.openlayers.api.Overlay;
import org.wicketstuff.openlayers.api.layer.Layer;
import org.wicketstuff.openlayers.api.layer.OSM;
import org.wicketstuff.openlayers.api.layer.GMap;
import org.wicketstuff.openlayers.api.layer.WFS;
import org.wicketstuff.openlayers.api.layer.WMS;
import org.wicketstuff.openlayers.api.layer.Vector;
import org.wicketstuff.openlayers.event.EventType;
import org.wicketstuff.openlayers.event.OverlayListenerBehavior;
import org.wicketstuff.openlayers.event.PopupListener;
import org.wicketstuff.openlayers.js.JSUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

/**
 * Wicket component to embed <a href="http://www.openlayers.org/">Openlayers Maps</a> into your
 * pages.
 */
public class OpenLayersMap extends Panel implements IOpenLayersMap {

    private static final String OPEN_LAYERS_VERSION = "2.13.1";

    private static Logger log = LoggerFactory.getLogger(OpenLayersMap.class);
    private String businessLogicProjection = null;

    private abstract class JSMethodBehavior extends Behavior {

        private static final long serialVersionUID = 1L;

        private final String attribute;

        public JSMethodBehavior(final String attribute) {
            this.attribute = attribute;
        }

        protected abstract String getJSinvoke();

        /**
         * @see Behavior#onComponentTag(org.apache.wicket.Component,
         *      org.apache.wicket.markup.ComponentTag)
         */
        @Override
        public void onComponentTag(Component component, ComponentTag tag) {
            String invoke = getJSinvoke();

            if (attribute.equalsIgnoreCase("href")) {
                invoke = "javascript:" + invoke;
            }

            tag.put(attribute, invoke);
        }
    }

    public class PanDirectionBehavior extends JSMethodBehavior {
        private static final long serialVersionUID = 1L;

        private final int dx;

        private final int dy;

        public PanDirectionBehavior(String event, final int dx, final int dy) {
            super(event);
            this.dx = dx;
            this.dy = dy;
        }

        @Override
        protected String getJSinvoke() {
            return getJSpanDirection(dx, dy);
        }
    }

    public class SetCenterBehavior extends JSMethodBehavior {
        private static final long serialVersionUID = 1L;

        private final LonLat gLatLng;
        private final Integer zoom;

        public SetCenterBehavior(String event, LonLat gLatLng, Integer zoom) {
            super(event);
            this.gLatLng = gLatLng;
            this.zoom = zoom;
        }

        @Override
        protected String getJSinvoke() {
            return getJSsetCenter(gLatLng, zoom);
        }
    }

    public class SetZoomBehavior extends JSMethodBehavior {
        private static final long serialVersionUID = 1L;

        private final Integer zoom;

        public SetZoomBehavior(final String event, final Integer zoom) {
            super(event);
            this.zoom = zoom;
        }

        @Override
        protected String getJSinvoke() {
            return getJSsetZoom(zoom);
        }
    }

    public class ZoomInBehavior extends JSMethodBehavior {
        private static final long serialVersionUID = 1L;

        public ZoomInBehavior(String event) {
            super(event);
        }

        @Override
        protected String getJSinvoke() {
            return getJSzoomIn();
        }
    }

    public class ZoomOutBehavior extends JSMethodBehavior {
        private static final long serialVersionUID = 1L;

        public ZoomOutBehavior(String event) {
            super(event);
        }

        @Override
        protected String getJSinvoke() {
            return getJSzoomOut();
        }
    }

    private static final long serialVersionUID = 1L;

    private Bounds bounds;

    private PopupListener callbackListener = null;

    private static final LonLat DEFAULT_CENTER = new SphericalMercatorLonLat(37.4419, -122.1419);
    private LonLat center = DEFAULT_CENTER;

    private final List<IJavascriptComponent> controls = new ArrayList<IJavascriptComponent>();

    private boolean externalControls = false;

    private InfoWindow infoWindow;

    private List<Layer> layers = new ArrayList<Layer>();

    private final WebMarkupContainer map;

    private HashMap<String, String> options = new HashMap<String, String>();

    private List<Overlay> overlays = new ArrayList<Overlay>();

    private static final int DEFAULT_ZOOM = 13;
    private int zoom = DEFAULT_ZOOM;

    // determines if the marker layer will be visible in the
    // OpenLayers.Control.LayerSwitcher
    private boolean showMarkersInLayerSwitcher = true;

    /**
     *
     * Constructs a map with a default layer : "OpenLayers WMS",
     * "http://labs.metacarta.com/wms/vmap0"
     *
     * @param id
     */
    public OpenLayersMap(final String id, boolean developmentMode) {
        this(id, new OpenLayersMapHeaderContributor(developmentMode, OPEN_LAYERS_VERSION), new ArrayList<Overlay>(),
                new ArrayList<Layer>(), new HashMap<String, String>());

        HashMap<String, String> layerOptions = new HashMap<String, String>();

        layerOptions.put("layers", JSUtils.getQuotedString("basic"));

        layers.add(new WMS("OpenLayers WMS", "http://labs.metacarta.com/wms/vmap0", layerOptions));
    }

    public OpenLayersMap(String id) {
        this(id, false);
    }

    /**
     * Construct.
     *
     * @param id
     */
    public OpenLayersMap(final String id, boolean developmentMode, List<Layer> defaultLayers,
            HashMap<String, String> options) {
        this(id, new OpenLayersMapHeaderContributor(developmentMode, OPEN_LAYERS_VERSION), new ArrayList<Overlay>(),
                defaultLayers, options);
    }

    public OpenLayersMap(final String id, List<Layer> defaultLayers, HashMap<String, String> options) {

        this(id, false, defaultLayers, options);
    }

    public OpenLayersMap(final String id, boolean developmentMode, List<Layer> defaultLayers,
            HashMap<String, String> options, List<Overlay> overlays) {
        this(id, new OpenLayersMapHeaderContributor(developmentMode, OPEN_LAYERS_VERSION), overlays, defaultLayers,
                options);
    }

    public OpenLayersMap(final String id, List<Layer> defaultLayers, HashMap<String, String> options,
            List<Overlay> overlays) {
        this(id, false, defaultLayers, options, overlays);
    }

    public OpenLayersMap(final String id, List<Layer> defaultLayers, HashMap<String, String> options,
            List<Overlay> overlays, PopupListener popupListener) {

        this(id, false, defaultLayers, options, overlays, popupListener);
    }

    public OpenLayersMap(final String id, boolean developmentMode, List<Layer> defaultLayers,
            HashMap<String, String> options, List<Overlay> overlays, PopupListener popupListener) {
        this(id, new OpenLayersMapHeaderContributor(developmentMode, OPEN_LAYERS_VERSION), overlays, popupListener,
                defaultLayers, options);
    }

    /**
     *
     * Popups up the window as default!
     *
     * is protected to allow subclasses to override the HeaderContributor that is used. @see
     * OpenLayersMapHeaderContributor
     *
     * @param id
     * @param headerContrib
     * @param overlays
     *
     */
    protected OpenLayersMap(final String id, final OpenLayersMapHeaderContributor headerContrib,
            List<Overlay> overlays, List<Layer> defaultLayers, HashMap<String, String> options) {
        this(id, headerContrib, overlays, new PopupListener(false) {
            private static final long serialVersionUID = 1L;

            @Override
            protected void onClick(AjaxRequestTarget target, Overlay overlay) {
                // make sure that info window is closed
                if (Marker.class.isInstance(overlay)) {
                    clickAndOpenPopup((Marker) overlay, target);
                }
            }
        }, defaultLayers, options);

    }

    /**
     * Construct.
     *
     * @param id
     * @param headerContrib
     * @param overlays
     */
    private OpenLayersMap(final String id, final OpenLayersMapHeaderContributor headerContrib,
            List<Overlay> overlays, PopupListener popupListener, List<Layer> defaultLayers,
            HashMap<String, String> options) {
        super(id);
        popupListener.setOpenLayersMap(this);

        this.overlays = overlays;
        layers = defaultLayers;
        this.options = options;

        // always add callbacklistener dont know if its gonna be used later on!
        callbackListener = popupListener;
        add(callbackListener);

        add(headerContrib);
        addHeaderContributorsForLayers(layers);
        add(new Behavior() {
            private static final long serialVersionUID = 1L;

            @Override
            public void renderHead(Component component, IHeaderResponse response) {
                response.render(OnDomReadyHeaderItem.forScript(getJSinit()));
            }
        });

        setInfoWindow(new InfoWindow());
        add(getInfoWindow());

        map = new WebMarkupContainer("map");
        map.setOutputMarkupId(true);
        add(map);
    }

    private void addHeaderContributorsForLayers(List<Layer> layers) {
        for (Layer layer : layers) {
            layer.bindHeaderContributors(this);
        }
    }

    /**
     * Add a control.
     *
     * @param control
     *            control to add
     * @return This
     */
    public OpenLayersMap addControl(IJavascriptComponent control) {
        controls.add(control);

        final JavaScriptResourceReference[] jsReferences = control.getJSResourceReferences();

        if (jsReferences != null && jsReferences.length > 0) {

            add(new Behavior() {

                /**
                 *
                 */
                private static final long serialVersionUID = 1L;

                @Override
                public void renderHead(Component c, IHeaderResponse response) {

                    for (JavaScriptResourceReference javascriptResourceReference : jsReferences) {
                        response.render(JavaScriptHeaderItem.forReference(javascriptResourceReference));
                    }
                }
            });

        }

        AjaxRequestTarget target = getRequestCycle().find(AjaxRequestTarget.class);

        if (target != null && findPage() != null) {
            target.appendJavaScript(control.getJSadd(OpenLayersMap.this));

            if (jsReferences != null && jsReferences.length > 0) {

                for (JavaScriptResourceReference javascriptResourceReference : jsReferences) {
                    target.getHeaderResponse()
                            .render(JavaScriptHeaderItem.forReference(javascriptResourceReference));
                }
            }

        }

        return this;
    }

    /**
     * Add an overlay.
     *
     * @param overlay
     *            overlay to add
     * @return This
     */
    public OpenLayersMap addOverlay(Overlay overlay) {
        overlays.add(overlay);
        for (OverlayListenerBehavior behavior : overlay.getBehaviors()) {
            add(behavior);
        }

        if (findPage() != null) {
            AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
            if (ajaxRequestTarget != null) {
                String jsToRun = getJsOverlay(overlay);
                ajaxRequestTarget.appendJavaScript(jsToRun);
            }
        }

        return this;
    }

    /**
     * Clear all overlays.
     *
     * @return This
     */
    public OpenLayersMap clearOverlays() {
        for (Overlay overlay : overlays) {
            for (OverlayListenerBehavior behavior : overlay.getBehaviors()) {
                remove(behavior);
            }
        }
        overlays.clear();

        if (findPage() != null) {
            AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
            if (ajaxRequestTarget != null) {
                ajaxRequestTarget.appendJavaScript(getJSinvoke("clearOverlays()"));
            }
        }
        return this;
    }

    public Bounds getBounds() {
        return bounds;
    }

    public PopupListener getCallbackListener() {
        return callbackListener;
    }

    public LonLat getCenter() {
        return center;
    }

    public List<IJavascriptComponent> getControls() {
        return controls;
    }

    /**
     * Generates the JavaScript used to instantiate this OpenlayersMap as an JavaScript class on the
     * client side.
     *
     * @return The generated JavaScript
     */
    protected String getJSinit() {

        StringBuffer js = new StringBuffer();

        if (options.size() > 0) {
            js.append("\nvar options = {");
            boolean first = true;
            for (String key : options.keySet()) {
                if (first) {
                    first = false;
                } else {
                    js.append(",\n");
                }
                js.append(key + ":" + options.get(key));
            }
            js.append("};\n");
            js.append("new WicketOMap('" + map.getMarkupId() + "', options, null, "
                    + String.valueOf(showMarkersInLayerSwitcher) + ");\n");
        } else {
            js.append("new WicketOMap('" + map.getMarkupId() + "', null, null, "
                    + String.valueOf(showMarkersInLayerSwitcher) + ");\n");
        }

        for (Layer layer : layers) {
            if (layer instanceof WMS) {
                WMS wms = (WMS) layer;
                js.append("var wms" + wms.getId() + " =" + wms.getJSconstructor() + ";\n");
                js.append(getJSinvoke("addLayer(wms" + wms.getId() + "," + wms.getId() + ")"));
            }
            if (layer instanceof GMap) {
                GMap gmap = (GMap) layer;
                js.append("var gmap" + gmap.getId() + " =" + gmap.getJSconstructor() + ";\n");
                js.append(getJSinvoke("addLayer(gmap" + gmap.getId() + "," + gmap.getId() + ")"));
            }
            if (layer instanceof OSM) {
                OSM osm = (OSM) layer;
                js.append("var osm" + osm.getId() + " =" + osm.getJSconstructor() + ";\n");
                js.append(getJSinvoke("addLayer(osm" + osm.getId() + "," + osm.getId() + ")"));
            }
            if (layer instanceof WFS) {
                WFS wfs = (WFS) layer;
                js.append("var wfs" + wfs.getId() + " =" + wfs.getJSconstructor() + ";\n");
                js.append(getJSinvoke("addLayer(wfs" + wfs.getId() + "," + wfs.getId() + ")"));

            }
            if (layer instanceof Vector) {
                Vector vec = (Vector) layer;
                js.append("var vec" + vec.getId() + " =" + vec.getJSconstructor() + ";\n");
                js.append(getJSinvoke("addLayer(vec" + vec.getId() + "," + vec.getId() + ")"));

            }
        }

        /*
         * If zoom and center are available then use them on the initial map rendering.
         */
        if (zoom != DEFAULT_ZOOM) {

            if (!center.equals(DEFAULT_CENTER))
                js.append(getJSsetCenter(center, zoom));
            else
                js.append(getJSsetZoom(zoom));
        } else {
            js.append(getJSinvoke("zoomToMaxExtent()"));
        }

        for (IJavascriptComponent control : controls) {
            js.append(control.getJSadd(this));
        }
        // Add the overlays.
        for (Overlay overlay : overlays) {

            js.append(getJsOverlay(overlay));
        }
        js.append(getJSinvoke("setPopupId('" + getInfoWindow().getContent().getMarkupId() + "')"));

        if (businessLogicProjection != null) {
            js.append(getJSSetBusinessLogicProjection());
        }

        return js.toString();
    }

    public String getJSInstance() {
        return "Wicket.omaps['" + map.getMarkupId() + "']";
    }

    /**
     * Convenience method for generating a JavaScript call on this Openlayermap with the given
     * invocation.
     *
     * @param invocation
     *            The JavaScript call to invoke on this Openlayermap.
     * @return The generated JavaScript.
     */
    // TODO Could this become default or protected?
    public String getJSinvoke(String invocation) {
        return "Wicket.omaps['" + map.getMarkupId() + "']." + invocation + ";\n";
    }

    public String getJSinvokeNoLineEnd(String invocation) {
        return "Wicket.omaps['" + map.getMarkupId() + "']." + invocation;
    }

    private String getJsOverlay(Overlay overlay) {
        String jsToRun = overlay.getJSadd(this) + "\n";
        if (overlay instanceof Marker) {
            Marker marker = (Marker) overlay;
            // if marker has popup and there are no events attached then attach
            // default listener
            if (marker.getPopup() != null && (marker.getEvents() == null || marker.getEvents().length == 0)) {
                // add mousedown listener!
                marker.addEvent(EventType.mousedown);
            }
            // add listeners
            for (EventType evt : marker.getEvents()) {
                jsToRun += getJSinvoke("addMarkerListener('" + evt.name() + "','"
                        + callbackListener.getCallBackForMarker(marker) + "'," + marker.getOverlayJSVar() + ")");
            }
            if (marker.getIcon() != null) {
                // prepend icon stuff
                jsToRun = marker.getIcon().getSize().getJSadd() + marker.getIcon().getOffset().getJSadd()
                        + marker.getIcon().getJSadd() + jsToRun;
            }
        }
        return jsToRun;

    }

    private String getJSpanDirection(int dx, int dy) {
        return getJSinvoke("panDirection(" + dx + "," + dy + ")");
    }

    private String getJSsetCenter(LonLat center, Integer zoom) {
        if (center != null && zoom != null)
            return getJSinvoke("setCenter(" + center.getJSconstructor() + ", " + zoom + ")");
        else
            return "";
    }

    private String getJSsetDoubleClickZoomEnabled(boolean enabled) {
        return getJSinvoke("setDoubleClickZoomEnabled(" + enabled + ")");
    }

    private String getJSsetDraggingEnabled(boolean enabled) {
        return getJSinvoke("setDraggingEnabled(" + enabled + ")");
    }

    private String getJSsetScrollWheelZoomEnabled(boolean enabled) {
        return getJSinvoke("setScrollWheelZoomEnabled(" + enabled + ")");
    }

    private String getJSsetZoom(Integer zoom) {
        return zoom != null ? getJSinvoke("setZoom(" + zoom + ")") : "";
    }

    private String getJSzoomIn() {
        return getJSinvoke("zoomIn()");
    }

    private String getJSzoomOut() {
        return getJSinvoke("zoomOut()");
    }

    public List<Layer> getLayers() {
        return layers;
    }

    public List<Overlay> getOverlays() {
        return Collections.unmodifiableList(overlays);
    }

    public Integer getZoom() {
        return zoom;
    }

    public boolean isExternalControls() {
        return externalControls;
    }

    /**
     * Remove a control.
     *
     * @param control
     *            control to remove
     * @return This
     */
    public OpenLayersMap removeControl(IJavascriptComponent control) {
        controls.remove(control);

        if (findPage() != null) {
            AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
            if (ajaxRequestTarget != null) {
                ajaxRequestTarget.appendJavaScript(control.getJSremove(OpenLayersMap.this));
            }
        }

        return this;
    }

    /**
     * Remove an overlay.
     *
     * @param overlay
     *            overlay to remove
     * @return This
     */
    public OpenLayersMap removeOverlay(Overlay overlay) {
        while (overlays.contains(overlay)) {
            overlays.remove(overlay);
        }
        for (OverlayListenerBehavior behavior : overlay.getBehaviors()) {
            remove(behavior);
        }

        if (findPage() != null) {
            AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
            if (ajaxRequestTarget != null) {
                ajaxRequestTarget.appendJavaScript(overlay.getJSremove(OpenLayersMap.this));
            }
        }

        return this;
    }

    /**
     * Set the center.
     *
     * @param center
     *            center to set
     */
    public void setCenter(LonLat center, Integer zoom) {
        if (!this.center.equals(center)) {
            this.center = center;
            this.zoom = zoom;

            if (findPage() != null) {
                AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
                if (ajaxRequestTarget != null) {
                    ajaxRequestTarget.appendJavaScript(getJSsetCenter(center, zoom));
                }
            }
        }
    }

    public void setExternalControls(boolean externalControls) {
        this.externalControls = externalControls;
    }

    public void setLayers(List<Layer> layers) {
        this.layers = layers;
    }

    public void setOverlays(List<Overlay> overlays) {
        clearOverlays();
        for (Overlay overlay : overlays) {
            addOverlay(overlay);
        }
    }

    public void setZoom(Integer level) {
        if (zoom != level) {
            zoom = level;

            if (findPage() != null) {
                AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
                if (ajaxRequestTarget != null) {
                    ajaxRequestTarget.appendJavaScript(getJSsetZoom(zoom));
                }
            }
        }
    }

    /**
     * Update state from a request to an AJAX target.
     */
    public void update(AjaxRequestTarget target) {
        Request request = RequestCycle.get().getRequest();

        // Attention: don't use setters as this will result in an endless
        // AJAX request loop
        center = LonLat
                .parseWithNames(request.getRequestParameters().getParameterValue("centerConverted").toString());
        zoom = Integer.parseInt(request.getRequestParameters().getParameterValue("zoomConverted").toString());
        bounds = Bounds
                .parseWithNames(request.getRequestParameters().getParameterValue("boundsConverted").toString());

        getInfoWindow().update(target);
    }

    public void setInfoWindow(InfoWindow infoWindow) {
        this.infoWindow = infoWindow;
    }

    public InfoWindow getInfoWindow() {
        return infoWindow;
    }

    /**
     * @see org.apache.wicket.Component#onRender()
     */
    @Override
    protected void onRender() {
        super.onRender();

        RuntimeConfigurationType configurationType = Application.get().getConfigurationType();
        if (configurationType.equals(RuntimeConfigurationType.DEVELOPMENT)
                && !Application.get().getMarkupSettings().getStripWicketTags()) {
            log.warn("Application is in DEVELOPMENT mode && Wicket tags are not stripped,"
                    + " Firefox 3.0 will not render the OMap."
                    + " Change to DEPLOYMENT mode  || turn on Wicket tags stripping." + " See:"
                    + " http://www.nabble.com/Gmap2-problem-with-Firefox-3.0-to18137475.html.");
        }
    }

    public void setBounds(Bounds bounds) {
        this.bounds = bounds;
    }

    public void setCenter(LonLat center) {
        setCenter(center, zoom);
    }

    /**
     * @param showMarkersInLayerSwitcher
     *            if true the internal markers layer will be visible in the
     *            OpenLayers.Control.LayerSwitcher
     *
     *            Default is true.
     *
     *            Set to false to hide the markers layer from the LayerSwitcher.
     */
    public void setShowMarkersInLayerSwitcher(boolean showMarkersInLayerSwitcher) {
        this.showMarkersInLayerSwitcher = showMarkersInLayerSwitcher;
    }

    public void setBusinessLogicProjection(String businessLogicProjection) {
        this.businessLogicProjection = businessLogicProjection;
        AjaxRequestTarget ajaxRequestTarget = getRequestCycle().find(AjaxRequestTarget.class);
        if (ajaxRequestTarget != null) {
            ajaxRequestTarget.appendJavaScript(getJSSetBusinessLogicProjection());
        }
    }

    public String getBusinessLogicProjection() {
        return businessLogicProjection;
    }

    private String getJSSetBusinessLogicProjection() {
        if (businessLogicProjection == null) {
            return getJSinvoke("setBusinessLogicProjection(null)");
        }
        return getJSinvoke("setBusinessLogicProjection('" + businessLogicProjection + "')");
    }
}