From 70ef87d2eef7e2deffee78acfe86650ae2e65720 Mon Sep 17 00:00:00 2001 From: Nikolaus Krismer <niko@krismer.de> Date: Wed, 12 Feb 2014 18:00:24 +0100 Subject: [PATCH] implemented configuration singleton (to prevent global variables) isochrone button on geosearch result now only shown if datastore is configured for clicked point (still some todos open) --- src/main/webapp/index.html | 42 +++------- src/main/webapp/js/isochrone/configuration.js | 32 ++++++++ src/main/webapp/js/isochrone/initHelper.js | 55 ++++++++++++++ .../webapp/js/isochrone/searchExtender.js | 76 +++++++++++++++++++ src/main/webapp/js/map/geosearch.js | 11 +-- src/main/webapp/js/map/isomap.js | 16 +++- .../webapp/js/service/serviceConfiguration.js | 69 +++++++++++------ 7 files changed, 234 insertions(+), 67 deletions(-) create mode 100644 src/main/webapp/js/isochrone/configuration.js create mode 100644 src/main/webapp/js/isochrone/initHelper.js create mode 100644 src/main/webapp/js/isochrone/searchExtender.js diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index d58dc28b..a4ab9cb7 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -31,48 +31,23 @@ <script type="text/javascript" src="js/map/isomap.js"></script> <script type="text/javascript" src="js/service/serviceConfiguration.js"></script> <script type="text/javascript" src="js/service/websocket.js"></script> + <script type="text/javascript" src="js/isochrone/configuration.js"></script> + <script type="text/javascript" src="js/isochrone/initHelper.js"></script> + <script type="text/javascript" src="js/isochrone/searchExtender.js"></script> <script type="text/javascript"> // TODO: Should we really work with global javascript variables (wouldn't listener also work)? var datasetCfgs = {}; - function init() { - var ws = new Websocket(); - var config = new Configuration(ws); - config.getFromServer(); - - var isoMap = new IsoMap('map'); - isoMap.locateAndDraw(); - } $(function() { - $('#help-dialog').dialog({ - autoOpen: false, - modal: true, - resizable: false - }); - - $('#settings-dialog').dialog({ - autoOpen: false, - modal: true, - resizable: false - }); - - $('#speed').spinner({ - step: 0.1, - numberFormat: "n" - }); + var h = new InitHelper(); - $('#settings-dialog select').css({'width':'100%'}); - $('#settings-dialog input').css({'width':'100%'}); - $('#datetime').datetimepicker({ - dateFormat: 'dd.mm.yy', - timeFormat: 'HH:mm' - }); - $('#datetime .ui-datepicker-today').click(); - }) + h.initPreferences(); + h.initMap(); + }); </script> </head> -<body onload="javascript:init();"> +<body> <div class="page-wrapper"> <div id="map" class="map"></div> <div id="help-dialog" title="Hilfe"> @@ -89,6 +64,7 @@ </select><br> <label for="name">Dataset</label><br> <select name="dataset" id="dataset" class="ui-widget-content ui-corner-all"> + <!-- TODO: Remove hard coded values and read them from server configuration --> <option value="BZ">Bozen</option> <option value="ST">Südtirol</option> <option value="IT">Italien</option> diff --git a/src/main/webapp/js/isochrone/configuration.js b/src/main/webapp/js/isochrone/configuration.js new file mode 100644 index 00000000..05bd67f4 --- /dev/null +++ b/src/main/webapp/js/isochrone/configuration.js @@ -0,0 +1,32 @@ +/** + * Singleton class configuration. + * This is used to store the client's configuration + */ +function Configuration() { + if (arguments.callee._singletonInstance) { + return arguments.callee._singletonInstance; + } + arguments.callee._singletonInstance = this; + + var datasetConfigMap = {}; + + this.addDatasetConfig = function(dSetConfig) { + if (!dSetConfig || !dSetConfig.datasetName) { + console.warn('Not adding invalid dataset configuration to client configuration singleton'); + console.debug(' - dSetConfig given:', dSetConfig); + return; + } + + datasetConfigMap[dSetConfig.datasetName] = dSetConfig; + }; + + this.getDatasetConfigMap = function() { + return datasetConfigMap; + }; + + this.getDatasetConfig = function(datasetName) { + return datasetConfigMap[datasetName]; + }; +}; + +new Configuration(); diff --git a/src/main/webapp/js/isochrone/initHelper.js b/src/main/webapp/js/isochrone/initHelper.js new file mode 100644 index 00000000..a50e92d9 --- /dev/null +++ b/src/main/webapp/js/isochrone/initHelper.js @@ -0,0 +1,55 @@ +function InitHelper() { + var config = null; + var isoMap = null; + var ws = null; + + // Public methods + + this.initMap = function() { + isoMap = new IsoMap('map'); + isoMap.locateAndDraw(); + + ws = new Websocket(); + config = new ServiceConfiguration(ws); + config.getFromServer(); + + $(document).on('isomap_draw', extendSearch.bind(this)); + }; + + this.initPreferences = function() { + $('#help-dialog').dialog({ + autoOpen: false, + modal: true, + resizable: false + }); + + $('#settings-dialog').dialog({ + autoOpen: false, + modal: true, + resizable: false + }); + + $('#speed').spinner({ + step: 0.1, + numberFormat: 'n' + }); + + $('#settings-dialog select').css({'width':'100%'}); + $('#settings-dialog input').css({'width':'100%'}); + $('#datetime').datetimepicker({ + dateFormat: 'dd.mm.yy', + timeFormat: 'HH:mm' + }); + $('#datetime .ui-datepicker-today').click(); + }; + + // Private methods + + extendSearch = function() { + var mapElem = isoMap.getMap(), + searchExtender; + + searchExtender = new SearchExtender(mapElem); + searchExtender.extendResult(isoMap.getMap()); + }; +}; \ No newline at end of file diff --git a/src/main/webapp/js/isochrone/searchExtender.js b/src/main/webapp/js/isochrone/searchExtender.js new file mode 100644 index 00000000..59b95d96 --- /dev/null +++ b/src/main/webapp/js/isochrone/searchExtender.js @@ -0,0 +1,76 @@ +function SearchExtender(map) { + var geosearchEventName = 'geosearch_showresult'; + var m = null; + + // Constructors + + m = map; + + // Public methods + + this.extendResult = function() { + m.on(geosearchEventName, onSearchResult.bind(this)); + }; + + // Private methods + + isochroneAvailable = function(point) { + var config = Configuration(), + configCandidates = [], + datasetConfigs = config.getDatasetConfigMap(); + + for (var dSetConfigName in datasetConfigs) { + var dSetConfig = datasetConfigs[dSetConfigName]; + if (dSetConfig.latLngBBox.contains(point)) { + configCandidates[configCandidates.length] = dSetConfig; + } + } + + // Check if only one datasets bbox contains the query point. + // If so... return the matching dataset config + var resultLength = configCandidates.length; + if (resultLength == 1) { + // TODO: Should we combine this with the dataset dropdown in the settings? + // Should we remove the combobox? + return configCandidates[0]; + } + + // at least two datasets are available for isochrone configuration + + // FIXME: How do we handle this? Should we combine this with the dataset dropdown in the settings (if so... how) + // By now we use the smallest dataset containing the point + var tmpCandidate = configCandidates[0]; + var tmpSize = tmpCandidate.bBox.getSize(); + for (var i = 1; i < resultLength; ++i) { + var cSize = configCandidates[i].bBox.getSize(); + if (cSize.x * cSize.y < tmpSize.x * tmpSize.y) { + tmpCandidate = configCandidates[i]; + tmpSize = cSize; + } + } + + return tmpCandidate; + }; + + onSearchResult = function(data) { + var divIcons, + iconIsochrone = null, + li; + + if (!data.queryPoint || !isochroneAvailable(data.queryPoint)) { + return; + } + + divIcons = document.createElement('div'); + li = $(data.element).find('li:first div'); + + iconIsochrone = document.createElement('div'); + iconIsochrone.id = 'icon-isochrone'; + iconIsochrone.className = 'icon-isochrone'; + iconIsochrone.appendChild(document.createTextNode('Isochrone')); + divIcons.className = 'geosearch-result-icons'; + divIcons.appendChild(iconIsochrone); + + $(divIcons).insertBefore(li); + }; +}; diff --git a/src/main/webapp/js/map/geosearch.js b/src/main/webapp/js/map/geosearch.js index 47877638..0dd3c68d 100644 --- a/src/main/webapp/js/map/geosearch.js +++ b/src/main/webapp/js/map/geosearch.js @@ -327,10 +327,8 @@ L.Control.GeoSearch = L.Control.extend({ _setResult: function(result) { var displayName = null, displayDescription = null, - divIcons = document.createElement('div'), divText = document.createElement('div'), elem = this._result, - iconIsochrone = null, index = -1; li = document.createElement('li'), txtDescription = null, @@ -354,20 +352,15 @@ L.Control.GeoSearch = L.Control.extend({ txtName = document.createElement('span'); txtName.className = 'result-name'; txtName.innerHTML = displayName.replace(/^\s+|\s+$/g,''); - iconIsochrone = document.createElement('div'); - iconIsochrone.id = 'icon-isochrone'; - iconIsochrone.className = 'icon-isochrone'; - iconIsochrone.appendChild(document.createTextNode('Isochrone')); - divIcons.className = 'geosearch-result-icons'; - divIcons.appendChild(iconIsochrone); divText.className = 'geosearch-result-text'; divText.appendChild(txtName); divText.appendChild(txtDescription); - li.appendChild(divIcons); li.appendChild(divText); elem.appendChild(li); elem.style.display = 'block'; + + this._map.fireEvent('geosearch_showresult', {element: elem, queryPoint: L.latLng({lat: parseFloat(result.lat), lon: parseFloat(result.lon)})}); }, _setSuggestList: function(results) { diff --git a/src/main/webapp/js/map/isomap.js b/src/main/webapp/js/map/isomap.js index c6feeb1f..c687d1c7 100644 --- a/src/main/webapp/js/map/isomap.js +++ b/src/main/webapp/js/map/isomap.js @@ -2,6 +2,7 @@ function IsoMap(divId) { var INNSBRUCK = [47.265718, 11.391342]; // var VIENNA = [48.186312, 16.317615]; var mapDivId = null; + var map = null; var mapOptions = { attributionControl: false, center: INNSBRUCK, @@ -14,11 +15,22 @@ function IsoMap(divId) { mapDivId = divId; + // Getter + + /** + * Gets the internal map object. + * This is only useful after the map has been drawn + * + * @return the map object. Null if the map has not been drawn. + */ + this.getMap = function() { + return map; + }; + // Public methods this.draw = function() { var layerControl = L.control.layers({position: 'topright'}), - map = null, zoomControl = L.control.zoom({position: 'bottomright'}); console.log('Drawing map'); @@ -45,6 +57,8 @@ function IsoMap(divId) { provider: new L.GeoSearch.Provider.OpenStreetMap(), showMarker: true })); + + $(document).trigger('isomap_draw'); }; this.locateAndDraw = function() { diff --git a/src/main/webapp/js/service/serviceConfiguration.js b/src/main/webapp/js/service/serviceConfiguration.js index 7b8f3005..f2cab3f9 100644 --- a/src/main/webapp/js/service/serviceConfiguration.js +++ b/src/main/webapp/js/service/serviceConfiguration.js @@ -1,4 +1,5 @@ -function Configuration(/* Websocket */ ws) { +function ServiceConfiguration(/* Websocket */ ws) { + var actionName = 'getConfiguration'; var websocket = null; // Constructor @@ -8,46 +9,66 @@ function Configuration(/* Websocket */ ws) { // Public methods this.getFromServer = function() { - var actionName = 'getConfiguration'; - $(document).on(actionName, function(jsonData) { - console.debug('Handling event "' + actionName + '":', jsonData); - datasetCfgs = readConfiguration(jsonData); - }); - + $(document).on(actionName, onServerResponse.bind(this)); websocket.sendWsMessage('{"action" : "' + actionName + '"}'); }; // Private methods - readConfiguration = function(data) { - // reads the enabled datasets and stores them in a client side config - // hashtable - // each dataset contains: - // - pointQ the a 2D-coordinate of the query point - // - timeQ the arrival or departure time of the query point - // - bbox the spatial extent of the specified area - var result = {}; - var datasets = data.defaultDatasets; + /** + * Reads the enabled datasets and stores them in a client side configuration singleton + * each dataset contains: + * <ul> + * <li>bbox the spatial extent of the specified area</li> + * <li>queryPoint the a 2D-coordinate of the query point</li> + * <li>time the arrival or departure time of the query point</li> + * </ul> + */ + onServerResponse = function(data) { + var config = Configuration(), + datasets = data.defaultDatasets, + result = {}; + + console.debug('Handling event "' + actionName + '":', data); for (var i = 0; i < datasets.length; i++) { var dataset = datasets[i]; - var cfgData = { - queryPoint: new L.LatLng(dataset.queryPoint.x, dataset.queryPoint.y), - bbox: new L.Bounds(dataset.bbox.minX, dataset.bbox.minY, dataset.bbox.maxX, dataset.bbox.maxY), + var boundLower = new L.Point(dataset.bbox.minX, dataset.bbox.minY); + var boundUpper = new L.Point(dataset.bbox.maxX, dataset.bbox.maxY); + var latlngLower = convertToLatLng(boundLower); + var latlngUpper = convertToLatLng(boundUpper); + + var dSetConfig = { + bBox: new L.Bounds(boundLower, boundUpper), + datasetName: dataset.name, date: dataset.date, - time: dataset.time, isoEdgeLayer: dataset.isoEdgeLayer, isoVertexLayer: dataset.isoVertexLayer, isoCoverageLayer: dataset.isoCoverageLayer, - prefix: dataset.prefix + latLngBBox: new L.latLngBounds(latlngLower, latlngUpper), + prefix: dataset.prefix, + queryPoint: new L.LatLng(dataset.queryPoint.x, dataset.queryPoint.y), + time: dataset.time }; + if (dataset.totalInhabitants) { - cfgData.totalInhabitants = dataset.totalInhabitants; + dSetConfig.totalInhabitants = dataset.totalInhabitants; } - result[dataset.name] = cfgData; + config.addDatasetConfig(dSetConfig); } - // config.contextPath = data.contextPath; return result; }; + + /** + * Converts a point from projection EPSG:3857 to LatLng (EPSG:4326) + * + * @see http://developer.tomtom.com/docs/read/map_toolkit/javascript_sdk_2_0/Migration_Guide + * @param L.Point the point to convert (containing EPSG:3857 x and y) + * @return L.latLng the matching L.latLng object (coordinates in EPSG:4326) + */ + convertToLatLng = function(point) { + var earthRadius = 6378137; + return L.Projection.SphericalMercator.unproject(point.divideBy(earthRadius)); + }; }; -- GitLab