com.modusoperandi.dragonfly.widgets.map.MapWidgetPage.java Source code

Java tutorial

Introduction

Here is the source code for com.modusoperandi.dragonfly.widgets.map.MapWidgetPage.java

Source

/**
 * Copyright (c) 2016 Modus Operandi, Inc.
 *
 * This is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details. A copy of the GNU Lesser General Public License is distributed along
 * with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package com.modusoperandi.dragonfly.widgets.map;

import com.spatial4j.core.context.SpatialContext;
import com.spatial4j.core.io.GeohashUtils;
import com.spatial4j.core.shape.Point;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang.StringEscapeUtils;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AbstractDefaultAjaxBehavior;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
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.WebPage;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.TextField;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.request.IRequestParameters;
import org.apache.wicket.request.Url;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.request.mapper.parameter.PageParameters;
import org.apache.wicket.util.time.Time;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.search.SearchType;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.transport.InetSocketTransportAddress;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import static org.elasticsearch.search.aggregations.AggregationBuilders.geohashGrid;
import org.elasticsearch.search.aggregations.bucket.geogrid.GeoHashGrid;

/**
 * The Class MapWidgetPage - renders the interactive map
 */
public class MapWidgetPage extends WebPage {

    public class HandleOnZoomLeafletCallbackAjaxBehavior extends AbstractDefaultAjaxBehavior {

        private static final long serialVersionUID = 1L;

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

            super.renderHead(component, response);

            final StringBuffer callbackJs = new StringBuffer();

            callbackJs.append("handleZoomCallbackUrl = '").append(this.getCallbackUrl()).append("';");

            response.render(OnDomReadyHeaderItem.forScript(callbackJs.toString()));
        }

        @Override
        protected void respond(final AjaxRequestTarget target) {

            final IRequestParameters parameters = getRequest().getRequestParameters();

            try {
                zoomLevel = Integer.parseInt(parameters.getParameterValue("zoom").toString("3"));
            } catch (final NumberFormatException e) {
                zoomLevel = 3;
            }
            zoomView = zoomLevel;

            // Only use square geohashes
            if (zoomLevel < 6) {
                zoomLevel = 3;
            } else {
                zoomLevel = 7;
            }

            try {
                south = Double.parseDouble(parameters.getParameterValue("south").toString("-90"));
                north = Double.parseDouble(parameters.getParameterValue("north").toString("90"));
                west = Double.parseDouble(parameters.getParameterValue("west").toString("-180"));
                east = Double.parseDouble(parameters.getParameterValue("east").toString("180"));
            } catch (final NumberFormatException e) {
                south = -90;
                north = 90;
                west = -180;
                east = 180;
            }

            center = parameters.getParameterValue("center").toString();

            refreshDataOnMap(heatMapSettingsPanel.getConfiguration(), target);
        }
    }

    private class AddAjaxButton extends AjaxButton {

        private static final long serialVersionUID = 1L;

        public AddAjaxButton() {

            super("addButton");
        }

        @Override
        protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {

            addWindow.show(target);
        }
    }

    private class PermalinkAjaxButton extends AjaxButton {

        private static final long serialVersionUID = 1L;

        public PermalinkAjaxButton() {

            super("permalinkButton");
        }

        @Override
        protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {

            informationPanel.setInformation("Permalink", generatePermalink(heatMapSettingsPanel.getConfiguration()),
                    target);
            informationWindow.show(target);
        }
    }

    private class RefreshAjaxButton extends AjaxButton {

        private static final long serialVersionUID = 1L;

        public RefreshAjaxButton() {

            super("refreshButton");
        }

        @Override
        protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {

            refreshDataOnMap(heatMapSettingsPanel.getConfiguration(), target);
        }
    }

    private transient static Client esClient = null;

    private static final Logger LOGGER = Logger.getLogger(MapWidgetPage.class.getName());
    private static final int MAX_GEOHASH_GRID = 20000;
    private static final int MAX_HITS_TO_SHOW = 2000;
    private static final int MAX_POPUP_ROW_LENGTH = 50;
    private static final int ONE_DAY_IN_SECONDS = 86400;
    private static final long serialVersionUID = 1L;

    public static void onDestroy() {

        if (esClient != null) {
            esClient.close();
            esClient = null;
        }
    }

    private static void addGeojsonToMap(final AjaxRequestTarget target, final String indexName,
            final String geojsonFieldName) {

        final StringBuffer js = new StringBuffer();

        final SearchRequestBuilder search = getEsClient().prepareSearch(indexName);
        search.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
        search.setQuery(QueryBuilders.matchAllQuery());
        search.setSize(MAX_HITS_TO_SHOW);

        final SearchResponse results = search.execute().actionGet();

        for (final SearchHit hit : results.getHits()) {
            final String valueStr = (String) hit.getSource().get(geojsonFieldName);

            if (valueStr != null) {

                target.appendJavaScript("geoJsonLayer.clearLayers();");

                js.setLength(0);
                js.append("geoJsonLayer.addData(");
                js.append(valueStr);
                js.append(");");

                target.appendJavaScript(js.toString());
            }
        }
    }

    private static void addWmsToMap(final AjaxRequestTarget target, final String tileUrl, final String layers,
            final String format, final String attribution, final String name) {

        final StringBuffer tileParams = new StringBuffer();
        tileParams.append("{");
        tileParams.append("layers: '").append(layers).append("',");
        tileParams.append("format: '").append(format).append("',");
        tileParams.append("attribution: '").append(attribution).append("',");
        tileParams.append("transparent: true");
        tileParams.append("}");

        final StringBuffer js = new StringBuffer();
        js.append("var wms = L.tileLayer.wms('").append(tileUrl).append("',").append(tileParams.toString())
                .append(");");
        js.append("controls.addOverlay(wms,'").append(name).append("');");
        js.append("wms.addTo(map);");
        target.appendJavaScript(wrapAjaxJs("addWmsToMap", js.toString()));
    }

    /**
     * Retrieves the Elasticsearch index mapping for the specified index
     *
     * @param indexName
     *            the index name
     * @return the index mapping
     */
    private static ImmutableOpenMap<String, MappingMetaData> esGetMappings(final String indexName) {

        final ClusterStateResponse clusterStateResponse = getEsClient().admin().cluster().prepareState().execute()
                .actionGet();

        return clusterStateResponse.getState().getMetaData().index(indexName).getMappings();
    }

    private static void generateHeatmapJs(final SearchResponse results, final StringBuffer heatmapJs) {

        heatmapJs.append("if(!(typeof heatmapLayer === 'undefined') && (heatmapLayer != null)) {");
        heatmapJs.append("heatmapLayer.setData({data:[");

        long min = Long.MAX_VALUE;
        long max = 2; // The minimum max
        boolean firstTime = true;
        final GeoHashGrid geoGrid = results.getAggregations().get("geohashgrid");
        for (final GeoHashGrid.Bucket cell : geoGrid.getBuckets()) {
            final String geohash = cell.getKeyAsString();
            final long bucketCount = cell.getDocCount();

            final Point hashCenter = GeohashUtils.decode(geohash, SpatialContext.GEO);

            if (!firstTime) {
                heatmapJs.append(",");
            }
            firstTime = false;

            heatmapJs.append("{lat: ");
            heatmapJs.append(hashCenter.getY());
            heatmapJs.append(", lng: ");
            heatmapJs.append(hashCenter.getX());
            heatmapJs.append(", count: ");
            heatmapJs.append(Long.toString(bucketCount));
            heatmapJs.append("}");

            if (bucketCount > max) {
                max = bucketCount;
            }

            if (bucketCount < min) {
                min = bucketCount;
            }
        }

        heatmapJs.append("],");

        heatmapJs.append("max: ");
        heatmapJs.append(Long.toString(max));
        heatmapJs.append(",");

        heatmapJs.append("min: ");
        heatmapJs.append(Long.toString(min - 1));
        heatmapJs.append(",");

        heatmapJs.append("});");
        heatmapJs.append("};");
    }

    private static Client getEsClient() {

        // ////////////
        // Remote
        if (esClient == null) {
            try {
                final Settings settings = Settings.settingsBuilder()
                        .put("node.name", "DRAGONFLY_" + Long.toString(Thread.currentThread().getId()))
                        .put("cluster.name", "DRAGONFLY").build();
                esClient = new TransportClient.Builder().settings(settings).build().addTransportAddress(
                        new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));
            } catch (UnknownHostException ex) {
                LOGGER.log(Level.SEVERE, null, ex);
            }
        }

        return esClient;
    }

    /**
     * Checks if the specified field is a link based on the Elasticsearch index mapping metadata
     *
     * @param field
     *            the field to check
     * @param meta
     *            the index mapping metadata
     * @return true, if is link
     */
    private static boolean isLink(final String fieldName, final Map<String, Object> meta) {

        String fieldInterface = "";

        if (meta != null) {
            for (final Entry<String, Object> entry : meta.entrySet()) {
                if (entry.getKey().equals("Fields")) {
                    // This is a field descriptor
                    try {
                        @SuppressWarnings("unchecked")
                        final ArrayList<Map<String, Object>> descriptors = (ArrayList<Map<String, Object>>) entry
                                .getValue();
                        for (final Map<String, Object> field : descriptors) {
                            if (field.get("Name").equals(fieldName)) {
                                fieldInterface = (String) field.get("Extended Type");
                            }
                        }
                    } catch (final Exception e) {
                        // The meta data is a JSON object created by a query widget converted to a map of maps.
                        // If the conventions were not followed, parsing will fail in many possible ways.
                        // If parsing fails, this field will be treated as a normal field

                        LOGGER.log(Level.SEVERE, e.getMessage(), e);
                    }
                }
            }
        }

        return fieldInterface.equals("URL");
    }

    private static String wrapAjaxJs(final String location, final String js) {

        final StringBuffer wrappedJs = new StringBuffer();
        wrappedJs.append("try {");
        wrappedJs.append(js);
        wrappedJs.append("} catch(err) { alert('ERROR at ").append(location).append(": ' + err); }");
        return wrappedJs.toString();
    }

    private final ModalWindow addWindow;
    private final ModalWindow informationWindow;
    private final String defaultTileServerConnection = "baseLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {\n"
            + "attribution: '&copy; <a href=\"http://openstreetmap.org\">OpenStreetMap</a> contributors, <a href=\"http://creativecommons.org/licenses/by-sa/2.0/\">CC-BY-SA</a>'\n"
            + "});\n";
    private double east = 180;
    private final List<String> fieldList = new LinkedList<>();
    private String filterTerm;
    private String geojsonField;
    private String geojsonIndex;
    private final MapSettingsPanel heatMapSettingsPanel;
    private final InformationPanel informationPanel;
    private String hits = "0";
    private final Label hitsField;
    private String hmRadius;
    private String hmTransparency;
    // private final WebMarkupContainer mapDiv;
    private String mapIndex;
    private String mapLocationField;
    private double north = 90;
    private double south = -90;
    private final TextField<String> termFilterField;
    private String tileServerConnection;
    private transient ImmutableOpenMap<String, MappingMetaData> typeMappings;
    private double west = -180;
    private String wmsAttribution;
    private String wmsFormat;
    private String wmsLayers;
    private String wmsName;
    private String wmsTileUrl;
    private int zoomLevel = 3;
    private int zoomView;
    private String center = "[0,0]";
    private final WebMarkupContainer controlBar;

    private void initializeFromSettingsFile() {

        final Properties properties = new Properties();
        FileInputStream propertyStream = null;
        try {
            System.out.println("Current Path: " + Paths.get(".").toAbsolutePath().normalize().toString());

            File propFile = new File("dragonfly/HeatMapWidget.properties");

            System.out.println(propFile.getAbsolutePath());

            propertyStream = new FileInputStream(propFile);
            properties.load(propertyStream);

            tileServerConnection = URLDecoder.decode(properties.getProperty("tileUrl", defaultTileServerConnection),
                    "UTF-8");

        } catch (final FileNotFoundException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);

        } catch (final IOException e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);

        } finally {
            if (propertyStream != null) {
                try {
                    propertyStream.close();

                } catch (final IOException e) {
                    LOGGER.log(Level.SEVERE, e.getMessage(), e);
                }
            }
        }
    }

    /**
     * Instantiates a new map widget page.
     *
     * @param parameters
     */
    public MapWidgetPage(final PageParameters parameters) {

        initializeFromSettingsFile();

        // /////////////////////////////
        // Elasticsearch
        getEsClient();

        // /////////////////////////////
        //
        // Main panel
        controlBar = new WebMarkupContainer("controlBar");
        controlBar.setOutputMarkupId(true);
        add(controlBar);

        final Form<?> queryForm = new Form<>("queryForm");
        controlBar.add(queryForm);

        queryForm.add(new AddAjaxButton());
        queryForm.add(new PermalinkAjaxButton());

        final RefreshAjaxButton refreshButton = new RefreshAjaxButton();
        queryForm.add(refreshButton);
        queryForm.setDefaultButton(refreshButton);

        termFilterField = new TextField<>("termFilter", new PropertyModel<String>(this, "filterTerm"));
        queryForm.add(termFilterField);

        hitsField = new Label("hits", new PropertyModel<>(this, "hits"));
        hitsField.setOutputMarkupId(true);
        queryForm.add(hitsField);

        // /////////////////////////////
        // Map
        // mapDiv = new WebMarkupContainer("heatmapDiv");
        // mapDiv.setOutputMarkupId(true);
        // add(mapDiv);
        add(new HandleOnZoomLeafletCallbackAjaxBehavior());

        // Add Panel
        addWindow = new ModalWindow("dataPanel");
        heatMapSettingsPanel = new MapSettingsPanel(addWindow.getContentId(), addWindow);
        addWindow.setContent(heatMapSettingsPanel);
        addWindow.setCookieName(null);
        addWindow.setInitialHeight(400);
        addWindow.setHeightUnit("px");
        addWindow.setInitialWidth(572);
        addWindow.setWidthUnit("px");
        addWindow.setResizable(false);
        addWindow.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {

            private static final long serialVersionUID = 8118424010410661292L;

            @Override
            public void onClose(final AjaxRequestTarget target) {

                if (heatMapSettingsPanel.isOk()) {
                    addDataToMap(heatMapSettingsPanel.getConfiguration(), target);
                }
            }
        });
        add(addWindow);

        // Information Panel
        informationWindow = new ModalWindow("informationPanel");
        informationPanel = new InformationPanel(informationWindow.getContentId());
        informationWindow.setContent(informationPanel);
        informationWindow.setCookieName(null);
        informationWindow.setInitialHeight(200);
        informationWindow.setHeightUnit("px");
        informationWindow.setInitialWidth(574);
        informationWindow.setWidthUnit("px");
        informationWindow.setResizable(false);

        add(informationWindow);
    }

    @Override
    public void renderHead(final IHeaderResponse response) {

        super.renderHead(response);

        response.render(JavaScriptHeaderItem.forScript(tileServerConnection, null));
    }

    private void addDataToHeatmap(final AjaxRequestTarget target, final String indexName, final String location) {

        final StringBuffer heatmapJs = new StringBuffer();
        final StringBuffer markerJs = new StringBuffer();
        hits = "0";

        SearchResponse results;

        if ((indexName != null) && !indexName.isEmpty()) {
            try {
                final SearchRequestBuilder aggSearch = getEsClient().prepareSearch(indexName);
                aggSearch.addAggregation(
                        geohashGrid("geohashgrid").field(location).precision(zoomLevel).size(MAX_GEOHASH_GRID));
                aggSearch.setSearchType(SearchType.COUNT);

                QueryBuilder query = QueryBuilders.matchAllQuery();
                if ((filterTerm != null) && !filterTerm.isEmpty()) {
                    query = QueryBuilders.matchQuery("_all", filterTerm);
                }

                if ((south > -90) && (north < 90) && (east < 180) && (west > -180)) {
                    final QueryBuilder geoQuery = QueryBuilders.geoBoundingBoxQuery(location).topLeft(north, west)
                            .bottomRight(south, east);
                    aggSearch.setQuery(QueryBuilders.boolQuery().must(query).must(geoQuery));

                } else {
                    aggSearch.setQuery(query);
                }

                results = aggSearch.execute().actionGet();

                generateHeatmapJs(results, heatmapJs);

                hits = Long.toString(results.getHits().getTotalHits());

                if (results.getHits().getTotalHits() < MAX_HITS_TO_SHOW) {
                    final SearchRequestBuilder search = getEsClient().prepareSearch(indexName);
                    search.setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
                    search.setSize(MAX_HITS_TO_SHOW);

                    if ((south > -90) && (north < 90) && (east < 180) && (west > -180)) {
                        final QueryBuilder geoQuery = QueryBuilders.geoBoundingBoxQuery(location)
                                .topLeft(north, west).bottomRight(south, east);
                        aggSearch.setQuery(QueryBuilders.boolQuery().must(query).must(geoQuery));

                    } else {
                        aggSearch.setQuery(query);
                    }

                    generateMarkerJs(search.execute().actionGet(), markerJs, location);
                }

            } catch (final ElasticsearchException e) {
                LOGGER.log(Level.SEVERE, "Invalid query - return empty results.", e);
            }
        }

        target.add(hitsField);

        if (markerJs.length() > 0) {
            target.appendJavaScript(wrapAjaxJs("addDataToMap - markers", markerJs.toString()));
        }

        target.appendJavaScript(wrapAjaxJs("addDataToMap - heatmap", heatmapJs.toString()));
    }

    private void addDataToMap(final Map<String, String> configuration, final AjaxRequestTarget target) {

        final String dataType = configuration.get("Type");

        if (dataType != null) {
            switch (dataType) {
            case "Query Results":
                mapIndex = configuration.get("Index");
                mapLocationField = configuration.get("Location");
                hmTransparency = configuration.get("HmTransparency");
                hmRadius = configuration.get("HmRadius");
                typeMappings = esGetMappings(mapIndex);
                addHeatmapToMap(target);
                addDataToHeatmap(target, mapIndex, mapLocationField);
                break;

            case "GeoJSON":
                geojsonIndex = configuration.get("Index");
                geojsonField = configuration.get("GeojsonField");
                addGeojsonToMap(target, geojsonIndex, geojsonField);
                break;

            case "WMS":
                wmsTileUrl = configuration.get("TileURL");
                wmsLayers = configuration.get("Layers");
                wmsFormat = configuration.get("Format");
                wmsAttribution = configuration.get("Attibution");
                wmsName = configuration.get("Name");
                addWmsToMap(target, wmsTileUrl, wmsLayers, wmsFormat, wmsAttribution, wmsName);
                break;

            default:
                LOGGER.log(Level.SEVERE, "Unknown dataType {0}", dataType);
                break;
            }
        }
    }

    private void setMapViewableArea(final Map<String, String> configuration, final AjaxRequestTarget target) {

        final StringBuilder js = new StringBuilder();

        js.append("\n\nmap.setView(L.latLng(").append(configuration.get("center")).append("), ")
                .append(configuration.get("zoomView")).append(");\n\n");

        target.appendJavaScript(wrapAjaxJs("setMapViewableArea", js.toString()));
    }

    private void addHeatmapToMap(final AjaxRequestTarget target) {

        final StringBuffer js = new StringBuffer();

        js.append("if(!(typeof heatmapLayer === 'undefined') && (heatmapLayer != null)) {");
        js.append("    map.removeLayer(heatmapLayer);");
        js.append("    controls.removeLayer(heatmapLayer);");
        js.append("    markerLayer.clearLayers(); ");
        js.append("}");
        js.append("heatmapLayer = new HeatmapOverlay({");
        js.append("                           radius: ").append(hmRadius).append(",");
        js.append("                           opacity: ").append(hmTransparency).append(",");
        js.append("                           scaleRadius: false,");
        js.append("                           useLocalExtrema: false,");
        js.append("                           latField: 'lat',");
        js.append("                           lngField: 'lng',");
        js.append("                           valueField: 'count'");
        js.append("                        });");
        js.append("map.addLayer(heatmapLayer);");
        js.append("controls.addOverlay(heatmapLayer,'Heat Map');");

        target.appendJavaScript(wrapAjaxJs("addHeatmapToMap", js.toString()));
    }

    @SuppressWarnings("unchecked")
    private void generateMarkerJs(final SearchResponse results, final StringBuffer markerJs,
            final String locationField) {

        for (final SearchHit hit : results.getHits().getHits()) {

            final GeoPoint location = new GeoPoint();
            location.resetFromString(hit.getSource().get(locationField).toString());

            final BigDecimal lat = new BigDecimal(location.lat()).setScale(11, BigDecimal.ROUND_HALF_DOWN);
            final BigDecimal lon = new BigDecimal(location.lon()).setScale(11, BigDecimal.ROUND_HALF_DOWN);

            markerJs.append("markerLayer.addLayer(L.marker([");
            markerJs.append(Double.toString(lat.doubleValue()));
            markerJs.append(",");
            markerJs.append(Double.toString(lon.doubleValue()));

            final StringBuffer markerText = new StringBuffer();
            hit.getSource().entrySet().stream().forEach((fields) -> {
                String markerValue = (String) fields.getValue();

                try {
                    markerText.append("<b>");
                    markerText.append(fields.getKey());
                    markerText.append(" :</b> ");

                    if (isLink(fields.getKey(), ((Map<String, Object>) typeMappings.get(hit.getType())
                            .getSourceAsMap().get("_meta")))) {

                        markerText.append("<a href=\"");
                        markerText.append(markerValue);
                        markerText.append("\">");
                        markerText.append(markerValue);
                        markerText.append("</a>");

                    } else {
                        if (markerValue.length() > MAX_POPUP_ROW_LENGTH) {
                            markerValue = markerValue.substring(0, MAX_POPUP_ROW_LENGTH) + " ...";
                        }
                        markerText.append(StringEscapeUtils.escapeHtml(markerValue).replace('\n', ' '));
                    }
                    markerText.append("<br>");

                } catch (final IOException e) {
                    LOGGER.log(Level.SEVERE, "Cannot access type mappings", e);
                } catch (final Exception e2) {
                    LOGGER.log(Level.SEVERE, e2.getMessage(), e2);
                }
            });

            markerJs.append("]).bindPopup('");
            markerJs.append(markerText.toString().replace("'", " "));
            markerJs.append("', {maxWidth: 500}));\n");
        }
    }

    private void refreshDataOnMap(final Map<String, String> configuration, final AjaxRequestTarget target) {

        target.appendJavaScript("clearMap()");

        if (mapIndex != null) {
            addDataToHeatmap(target, mapIndex, mapLocationField);
        }

        if (geojsonIndex != null) {
            addGeojsonToMap(target, geojsonIndex, geojsonField);
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.wicket.markup.html.WebPage#onAfterRender()
     */
    @Override
    protected void onAfterRender() {

        super.onAfterRender();

        // Increase the HTTP session timeout to 1 day
        ((HttpServletRequest) RequestCycle.get().getRequest().getContainerRequest()).getSession()
                .setMaxInactiveInterval(ONE_DAY_IN_SECONDS);
    }

    protected @Override void setHeaders(final org.apache.wicket.request.http.WebResponse response) {

        super.setHeaders(response);

        response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
        response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
        response.setDateHeader("Expires", Time.START_OF_UNIX_TIME); // Proxies.
    }

    private String generatePermalink(final Map<String, String> configuration) {

        configuration.put("zoomLevel", Integer.toString(zoomLevel));
        configuration.put("zoomView", Integer.toString(zoomView));
        configuration.put("center", center);
        configuration.put("tileUrl", tileServerConnection);
        configuration.put("filter", filterTerm);
        configuration.put("north", Double.toString(north));
        configuration.put("south", Double.toString(south));
        configuration.put("east", Double.toString(east));
        configuration.put("west", Double.toString(west));

        StringBuilder permalink = new StringBuilder();

        permalink.append(getRequestUrl());
        permalink.append("?");

        boolean firstTime = true;
        for (Entry<String, String> parameter : configuration.entrySet()) {
            try {
                if (parameter.getValue() != null) {
                    if (!firstTime) {
                        permalink.append("&");
                    }
                    firstTime = false;

                    permalink.append(URLEncoder.encode(parameter.getKey(), "UTF-8"));
                    permalink.append("=");
                    permalink.append(URLEncoder.encode(parameter.getValue(), "UTF-8"));
                }

            } catch (UnsupportedEncodingException e) {
                LOGGER.log(Level.SEVERE, e.getMessage(), e);
            }
        }

        return permalink.toString();
    }

    private String getRequestUrl() {

        return RequestCycle.get().getUrlRenderer()
                .renderFullUrl(Url.parse(urlFor(MapWidgetBMPage.class, null).toString()));
    }
}