nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit bc6da9b9542ac4b3354716d3927d5a05f8f3df03
parent 74cc415a621057fc6f37418b306b2fa32b07c94b
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Tue, 22 Nov 2016 11:01:11 +0300

[westra passes] code refactoring

Diffstat:
Mpackage.json | 1+
Asrc/lib/iconFromBackgroundImage/iconFromBackgroundImage.js | 27+++++++++++++++++++++++++++
Msrc/lib/leaflet.layer.canvasMarkers/canvasMarkers.js | 308+++++++++++++++++++++++++++++++++----------------------------------------------
Asrc/lib/leaflet.layer.geojson-ajax/geojson-ajax.js | 32++++++++++++++++++++++++++++++++
Msrc/lib/leaflet.layer.westraPasses/westraPasses.js | 331++-----------------------------------------------------------------------------
Asrc/lib/leaflet.layer.westraPasses/westraPassesMarkers.js | 232+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/lib/xhr-promise/xhr-promise.js | 20++++++++++++--------
7 files changed, 438 insertions(+), 513 deletions(-)

diff --git a/package.json b/package.json @@ -52,6 +52,7 @@ "dependencies": { "browser-filesaver": "^1.1.1", "escape-html": "^1.0.3", + "image-promise": "^4.0.1", "knockout": "^3.4.0", "leaflet": "^1.0.1", "load-script": "^1.0.0", diff --git a/src/lib/iconFromBackgroundImage/iconFromBackgroundImage.js b/src/lib/iconFromBackgroundImage/iconFromBackgroundImage.js @@ -0,0 +1,26 @@ +function cached(f) { + var cache = {}; + return function(arg) { + if (!(arg in cache)) { + cache[arg] = f(arg); + } + return cache[arg]; + } +} + +function iconFromBackgroundImage(className) { + const container = document.createElement('div'); + container.style.position = 'absolute'; + document.body.appendChild(container); + const el = document.createElement('div'); + el.className = className; + container.appendChild(el); + const st = window.getComputedStyle(el), + url = st.backgroundImage.replace(/^url\("?/, '').replace(/"?\)$/, ''); + let icon = {'url': url, 'center': [-el.offsetLeft, -el.offsetTop]}; + document.body.removeChild(container); + container.removeChild(el); + return icon; +} + +export default cached(iconFromBackgroundImage); +\ No newline at end of file diff --git a/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.js b/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.js @@ -1,43 +1,35 @@ import L from 'leaflet'; import './canvasMarkers.css'; import rbush from 'rbush'; +import loadImage from 'image-promise'; /* Marker definition: { latlng: L.Latlng, icon: {url: string, center: [x, y]} or function(marker) returning icon, - label: sting or function, + label: string or function, tooltip: string or function, any other fields } */ -function cached(f) { - var cache = {}; - return function(arg) { - if (!(arg in cache)) { - cache[arg] = f(arg); +function calcIntersectionSum(rect, rects) { + let sum = 0, + left, right, top, bottom, rect2; + + for (rect2 of rects) { + left = Math.max(rect.minX, rect2.minX); + right = Math.min(rect.maxX, rect2.maxX); + top = Math.max(rect.minY, rect2.minY); + bottom = Math.min(rect.maxY, rect2.maxY); + if (top < bottom && left < right) { + sum += ((right - left) * (bottom - top)); } - return cache[arg]; } + return sum; } -function iconFromBackgroundUrl(className) { - var container = L.DomUtil.create('div', '', document.body), - el = L.DomUtil.create('div', className, container), - st = window.getComputedStyle(el), - url = st.backgroundImage.replace(/^url\("?/, '').replace(/"?\)$/, ''), - icon; - container.style.position = 'absolute'; - icon = {'url': url, 'center': [-el.offsetLeft, -el.offsetTop]}; - document.body.removeChild(container); - container.removeChild(el); - return icon; -} - -L.Util.iconFromBackgroundUrl = cached(iconFromBackgroundUrl); - L.Layer.CanvasMarkers = L.GridLayer.extend({ options: { async: true, @@ -50,10 +42,10 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ initialize: function(markers, options) { L.GridLayer.prototype.initialize.call(this, options); this.rtree = rbush(9, ['.latlng.lng', '.latlng.lat', '.latlng.lng', '.latlng.lat']); - this._regions = rbush(9, ['[0]', '[1]', '[2]', '[3]']); + this._regions = rbush(); this._iconPositions = {}; this._labelPositions = {}; - this._zoom = null; + this._labelPositionsZoom = null; this.addMarkers(markers); this._images = {}; this._tileQueue = []; @@ -66,7 +58,7 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ if (markers) { this.rtree.load(markers); this.resetLabels(); - setTimeout(this.redraw.bind(this), 0); + setTimeout(() => this.redraw(), 0); } }, @@ -74,7 +66,7 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ // FIXME: adding existing marker must be be noop this.rtree.insert(marker); this.resetLabels(); - setTimeout(this.redraw.bind(this), 0); + setTimeout(() => this.redraw(), 0); }, removeMarker: function(marker) { @@ -82,17 +74,12 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ }, removeMarkers: function(markers) { - var i, marker, markerId; - for (i = 0; i < markers.length; i++) { - marker = markers[i]; - this.rtree.remove(marker); - } + markers.forEach((marker) => this.rtree.remove(marker)); this.resetLabels(); - setTimeout(this.redraw.bind(this), 0); + setTimeout(() => this.redraw(), 0); }, updateMarkers: function(markers) { - var i; this.removeMarkers(markers); this.addMarkers(markers); }, @@ -112,41 +99,23 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ }, findLabelPosition: function(iconCenter, iconSize, textWidth, textHeight) { - var verticalPadding = 0, + const + verticalPadding = 0, xPositions = [iconCenter[0] + iconSize[0] / 2 + 2, iconCenter[0] - iconSize[0] / 2 - textWidth - 2], - yPositions = [iconCenter[1] - textHeight / 2 + verticalPadding, + yPositions = [ + iconCenter[1] - textHeight / 2 + verticalPadding, iconCenter[1] - textHeight * .75 - iconSize[1] / 4 + verticalPadding, iconCenter[1] - textHeight / 4 + iconSize[1] / 4 + verticalPadding, iconCenter[1] - textHeight - iconSize[1] / 2 + verticalPadding, iconCenter[1] + iconSize[1] / 2 + verticalPadding - ], i, j, bestX, bestY, minIntersectionSum, intersectionSum, x, y; - - var self = this; - - function calcIntersectionSum(rect) { - var regions = self._regions.search({minX: rect[0], minY: rect[1], maxX: rect[2], maxY: rect[3]}), - sum = 0, - k, left, right, top, bottom, rect2; - - for (k = 0; k < regions.length; k++) { - rect2 = regions[k]; - left = Math.max(rect[0], rect2[0]); - right = Math.min(rect[2], rect2[2]); - top = Math.max(rect[1], rect2[1]); - bottom = Math.min(rect[3], rect2[3]); - if (top < bottom && left < right) { - sum += ((right - left) * (bottom - top)); - } - } - return sum; - } - - minIntersectionSum = 1e10; - for (i = 0; i < xPositions.length; i++) { - x = xPositions[i]; - for (j = 0; j < yPositions.length; j++) { - y = yPositions[j]; - intersectionSum = calcIntersectionSum([x, y, x + textWidth, y + textHeight]); + ]; + + let minIntersectionSum = +Infinity; + let bestX, bestY; + for (let x of xPositions) { + for (let y of yPositions) { + const rect = {minX: x, minY: y, maxX: x + textWidth, maxY: y + textHeight}; + let intersectionSum = calcIntersectionSum(rect, this._regions.search(rect)); if (intersectionSum < minIntersectionSum) { minIntersectionSum = intersectionSum; bestX = x; @@ -160,45 +129,20 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ } } } - return [bestX, bestY]; }, - _iconPreloadFinished: function() { - var url; - for (url in this._images) { - if (!this._images[url].complete) { - return false; - } - } - return true; - }, - - _processTilesQueue: function() { - while (this._tileQueue.length) { - (this._tileQueue.pop())(); - } - }, - - preloadIcons: function(urls, cb) { - this._tileQueue.push(cb); - var self = this, - url, i, img; - for (i = 0; i < urls.length; i++) { - url = urls[i]; - if (!(url in this._images)) { - img = new Image(); - this._images[url] = img; - img.onload = function() { - if (self._iconPreloadFinished()) { - self._processTilesQueue(); + preloadIcons: function(urls) { + const newUrls = urls.filter((url) => !(url in this._images)); + if (newUrls.length) { + return loadImage(newUrls).then((images) => { + for (let image of images) { + this._images[image.src] = image; } - }; - img.src = url; - } - } - if (self._iconPreloadFinished()) { - self._processTilesQueue(); + } + ) + } else { + return Promise.resolve(); } }, @@ -206,31 +150,36 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ const canvas = L.DomUtil.create('canvas', 'leaflet-tile'); canvas.width = this.options.tileSize; canvas.height = this.options.tileSize; - this.drawTile(canvas, coords, coords.z); + this.drawTile(canvas, coords); return canvas; }, - drawTile: function(canvas, tilePoint, zoom) { - var tileSize = this.options.tileSize, - tileN = tilePoint.y * tileSize, - tileW = tilePoint.x * tileSize, + drawTile: function(canvas, coords) { + const + zoom = coords.z, + tileSize = this.options.tileSize, + tileN = coords.y * tileSize, + tileW = coords.x * tileSize, tileS = tileN + tileSize, - tileE = tileW + tileSize, - + tileE = tileW + tileSize; + const iconsHorPad = 520, iconsVertPad = 50, labelsHorPad = 256, - labelsVertPad = 20, - iconsBounds = L.latLngBounds(this._map.unproject([tileW - iconsHorPad, tileS + iconsHorPad], zoom), + labelsVertPad = 20; + const + iconsBounds = L.latLngBounds( + this._map.unproject([tileW - iconsHorPad, tileS + iconsHorPad], zoom), this._map.unproject([tileE + iconsHorPad, tileN - iconsVertPad], zoom) ), - labelsBounds = L.latLngBounds(this._map.unproject([tileW - labelsHorPad, tileS + labelsHorPad], zoom), + labelsBounds = L.latLngBounds( + this._map.unproject([tileW - labelsHorPad, tileS + labelsHorPad], zoom), this._map.unproject([tileE + labelsHorPad, tileN - labelsVertPad], zoom) - ), - iconUrls = [], - markerJobs = {}, - marker, p, icon, markerId, img; + ); + const + iconUrls = [], + markerJobs = {}; var markers = this.rtree.search( { minX: iconsBounds.getWest(), @@ -240,92 +189,92 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ } ); - for (var i = 0; i < markers.length; i++) { - marker = markers[i]; - p = this._map.project(marker.latlng, zoom); - icon = marker.icon; + for (let marker of markers) { + const p = this._map.project(marker.latlng, zoom); + let icon = marker.icon; if (typeof icon === 'function') { icon = icon(marker); } iconUrls.push(icon.url); - markerId = L.stamp(marker); + let markerId = L.stamp(marker); markerJobs[markerId] = {marker: marker, icon: icon, projectedXY: p}; } - var self = this; - this.preloadIcons(iconUrls, function() { - if (!self._map) { + this.preloadIcons(iconUrls).then(() => { + if (!this._map) { return; } - var textHeight = self.options.labelFontSize, - markerId, i, regionsInTile, isLabel, job, x, y, imgW, imgH, - label, textWidth, ctx, p; - if (self._zoom != zoom) { - self._zoom = zoom; - self.resetLabels(); + const textHeight = this.options.labelFontSize; + if (this._labelPositionsZoom !== zoom) { + this._labelPositionsZoom = zoom; + this.resetLabels(); } - ctx = canvas.getContext('2d'); + const ctx = canvas.getContext('2d'); ctx.font = L.Util.template('bold {size}px {name}', - {'name': self.options.labelFontName, 'size': self.options.labelFontSize} + {'name': this.options.labelFontName, 'size': this.options.labelFontSize} ); - for (markerId in markerJobs) { - job = markerJobs[markerId]; - img = self._images[job.icon.url]; + for (let markerId of Object.keys(markerJobs)) { + const job = markerJobs[markerId]; + let img = this._images[job.icon.url]; job.img = img; - imgW = Math.round(img.width * self.options.iconScale); - imgH = Math.round(img.height * self.options.iconScale); - if (!(markerId in self._iconPositions)) { - x = job.projectedXY.x - job.icon.center[0] * self.options.iconScale; - y = job.projectedXY.y - job.icon.center[1] * self.options.iconScale; + const imgW = Math.round(img.width * this.options.iconScale); + const imgH = Math.round(img.height * this.options.iconScale); + if (!(markerId in this._iconPositions)) { + let x = job.projectedXY.x - job.icon.center[0] * this.options.iconScale; + let y = job.projectedXY.y - job.icon.center[1] * this.options.iconScale; x = Math.round(x); y = Math.round(y); - self._iconPositions[markerId] = [x, y]; - self._regions.insert([x, y, x + imgW, y + imgH, job.marker, false]); + this._iconPositions[markerId] = [x, y]; + this._regions.insert({ + minX: x, minY: y, maxX: x + imgW, maxY: y + imgH, + marker: job.marker, isLabel: false + } + ); } - p = self._iconPositions[markerId]; - x = p[0]; - y = p[1]; + let [x, y] = this._iconPositions[markerId]; job.iconCenter = [x + imgW / 2, y + imgH / 2]; job.iconSize = [imgW, imgH]; } - markers = self.rtree.search({ - minX: labelsBounds.getWest(), minY: labelsBounds.getSouth(), - maxX: labelsBounds.getEast(), maxY: labelsBounds.getNorth() - } - ); - for (i = 0; i < markers.length; i++) { - marker = markers[i]; - markerId = L.stamp(marker); - job = markerJobs[markerId]; - label = job.marker.label; + markers = this.rtree.search({ + minX: labelsBounds.getWest(), minY: labelsBounds.getSouth(), + maxX: labelsBounds.getEast(), maxY: labelsBounds.getNorth() + } + ); + for (let marker of markers) { + const markerId = L.stamp(marker); + const job = markerJobs[markerId]; + let label = job.marker.label; if (label) { if (typeof label === 'function') { label = label(job.marker); } job.label = label; - if (!(markerId in self._labelPositions)) { - textWidth = ctx.measureText(label).width; - p = self.findLabelPosition(job.iconCenter, job.iconSize, textWidth, textHeight); - self._labelPositions[markerId] = p; - x = p[0]; - y = p[1]; - self._regions.insert([x, y, x + textWidth, y + textHeight, job.marker, true]); + if (!(markerId in this._labelPositions)) { + const textWidth = ctx.measureText(label).width; + const p = this.findLabelPosition(job.iconCenter, job.iconSize, textWidth, textHeight); + this._labelPositions[markerId] = p; + let [x, y] = p; + this._regions.insert({ + minX: x, minY: y, maxX: x + textWidth, maxY: y + textHeight, + marker: job.marker, isLabel: true + } + ); + } } else { - self._labelPositions[markerId] = null; + this._labelPositions[markerId] = null; } } - regionsInTile = self._regions.search({minX: tileW, minY: tileN, maxX: tileE, maxY: tileS}); - for (i = 0; i < regionsInTile.length; i++) { - isLabel = regionsInTile[i][5]; - if (isLabel) { + const regionsInTile = this._regions.search({minX: tileW, minY: tileN, maxX: tileE, maxY: tileS}); + // draw labels + for (let region of regionsInTile) { + if (region.isLabel) { //TODO: set font name ant size in options - marker = regionsInTile[i][4]; - markerId = L.stamp(marker); - job = markerJobs[markerId]; - p = self._labelPositions[markerId]; - x = p[0] - tileW; - y = p[1] - tileN; + const markerId = L.stamp(region.marker); + const job = markerJobs[markerId]; + const p = this._labelPositions[markerId]; + const x = p[0] - tileW; + const y = p[1] - tileN; ctx.textBaseline = 'bottom'; ctx.shadowColor = '#fff'; ctx.strokeStyle = '#fff'; @@ -337,19 +286,17 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ ctx.fillText(job.label, x, y + textHeight); } } - for (i = 0; i < regionsInTile.length; i++) { - isLabel = regionsInTile[i][5]; - if (!isLabel) { - marker = regionsInTile[i][4]; - markerId = L.stamp(marker); - job = markerJobs[markerId]; - p = self._iconPositions[markerId]; - x = p[0] - tileW; - y = p[1] - tileN; + // draw icons + for (let region of regionsInTile) { + if (!region.isLabel) { + const markerId = L.stamp(region.marker); + const job = markerJobs[markerId]; + const p = this._iconPositions[markerId]; + const x = p[0] - tileW; + const y = p[1] - tileN; ctx.drawImage(job.img, x, y, job.iconSize[0], job.iconSize[1]); } } - // setTimeout(() => callback(canvas), 0); } ); return this; @@ -366,7 +313,7 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ region = this._regions.search({minX: p.x, minY: p.y, maxX: p.x, maxY: p.y})[0], marker; if (region) { - marker = region[4]; + marker = region.marker; } else { marker = null; } @@ -439,7 +386,6 @@ L.Layer.CanvasMarkers = L.GridLayer.extend({ }, onAdd: function(map) { - map.createPane('rasterMarker').style.zIndex = 550; L.GridLayer.prototype.onAdd.call(this, map); map.on('mousemove', this.onMouseMove, this); map.on('mouseout', this.onMouseOut, this); diff --git a/src/lib/leaflet.layer.geojson-ajax/geojson-ajax.js b/src/lib/leaflet.layer.geojson-ajax/geojson-ajax.js @@ -0,0 +1,32 @@ +import L from 'leaflet'; +import {prepareXMLHttpRequestPromise} from 'lib/xhr-promise/xhr-promise'; + +L.Layer.GeoJSONAjax = L.GeoJSON.extend({ + options: { + requestTimeout: 30000 + }, + + initialize: function(url, options) { + L.GeoJSON.prototype.initialize.call(this, null, options); + this.url = url; + const {promise, send} = prepareXMLHttpRequestPromise( + this.url, {responseType: 'json', timeout: this.options.requestTimeout}); + promise.then((xhr) => this.addData(xhr.response)) + this._loadData = send; + }, + + loadData: function() { + if (this._loadStarted) { + return; + } + this._loadStarted = true; + this._loadData(); + }, + + onAdd: function(map) { + L.GeoJSON.prototype.onAdd.call(this, map); + this.loadData(); + } + } +); + diff --git a/src/lib/leaflet.layer.westraPasses/westraPasses.js b/src/lib/leaflet.layer.westraPasses/westraPasses.js @@ -1,79 +1,7 @@ import L from 'leaflet'; -import openPopup from 'lib/popupWindow/popupWindow'; import './westraPasses.css'; -import 'lib/leaflet.layer.canvasMarkers/canvasMarkers' -import escapeHtml from 'escape-html'; -import {saveAs} from 'browser-filesaver'; - -L.Util.AjaxLoader = L.Class.extend({ - initialize: function(url, callback, xhrOptions) { - this.isLoading = false; - this.hasLoaded = false; - this.url = url; - this.callback = callback; - this.options = xhrOptions; - }, - - tryLoad: function() { - if (this.isLoading || this.hasLoaded) { - return; - } - this.isLoading = true; - var xhr = new XMLHttpRequest(); - xhr.open('GET', this.url); - L.extend(xhr, this.options); - var self = this; - xhr.onreadystatechange = function() { - if (xhr.readyState == 4) { - if (xhr.status == 200 && xhr.response) { - self.callback(xhr); - self.hasLoaded = true; - } else { - console.log('Failed getting data for geojson layer from url', self.url) - } - self.isLoading = false; - } - }; - xhr.send(); - } - } -); - -L.Util.ajaxLoader = function(url, callback, options) { - return new L.Util.AjaxLoader(url, callback, options); -}; - - -L.GeoJSONAjax = L.GeoJSON.extend({ - options: { - requestTimeout: 10000 - }, - - initialize: function(url, options) { - L.GeoJSON.prototype.initialize.call(this, null, options); - this.url = url; - this.loader = L.Util.ajaxLoader(url, this.onDataLoaded.bind(this), { - responseType: 'json', timeout: this.options.requestTimeout - } - ); - }, - - onAdd: function(map) { - L.GeoJSON.prototype.onAdd.call(this, map); - this.loadData; - }, - - loadData: function() { - this.loader.tryLoad(); - }, - - onDataLoaded: function(xhr) { - this.addData(xhr.response); - this.fireEvent('loaded'); - } - } -); - +import 'lib/leaflet.layer.geojson-ajax/geojson-ajax'; +import westraPasesMarkers from './westraPassesMarkers'; L.Layer.WestraPasses = L.Layer.extend({ options: { @@ -84,13 +12,13 @@ L.Layer.WestraPasses = L.Layer.extend({ initialize: function(baseUrl, options) { L.setOptions(this, options); - this.markers = new westraPasesMarkers(baseUrl); - this.regions1 = new L.GeoJSONAjax(baseUrl + this.options.fileRegions1, { + this.markers = new westraPasesMarkers(baseUrl, {pane: 'rasterMarker'}); + this.regions1 = new L.Layer.GeoJSONAjax(baseUrl + this.options.fileRegions1, { className: 'westra-region-polygon', onEachFeature: this._setRegionLabel.bind(this, 'regions1') } ); - this.regions2 = new L.GeoJSONAjax(baseUrl + this.options.fileRegions2, { + this.regions2 = new L.Layer.GeoJSONAjax(baseUrl + this.options.fileRegions2, { className: 'westra-region-polygon', onEachFeature: this._setRegionLabel.bind(this, 'regions2') } @@ -113,10 +41,6 @@ L.Layer.WestraPasses = L.Layer.extend({ labelMarker.on('click', zoomToRegion, this); }, - // setZIndex: function(z) { - // this.markers.setZIndex(z + this.options.zIndexOffset || 0); - // }, - setLayersVisibility: function(e) { if (!this._map) { return; @@ -152,9 +76,10 @@ L.Layer.WestraPasses = L.Layer.extend({ onAdd: function(map) { this._map = map; + map.createPane('rasterMarker').style.zIndex = 550; + this.markers.loadData(); this.regions1.loadData(); this.regions2.loadData(); - this.markers.loadData(); this.setLayersVisibility(); map.on('zoomend', this.setLayersVisibility, this); map.on('zoomanim', this.setLayersVisibility, this); @@ -167,251 +92,9 @@ L.Layer.WestraPasses = L.Layer.extend({ this._map.off('zoomend', this.setLayersVisibility, this); this._map.off('zoomanim', this.setLayersVisibility, this); this._map = null; - }, - - clone: function() { - return this.markers.clone(); } - } ); -var westraPasesMarkers = L.Layer.CanvasMarkers.extend({ - options: { - filePasses: 'westra_passes.json', - scaleDependent: true - }, - - readyEvent: 'rendered', - - initialize: function(baseUrl, options) { - L.Layer.CanvasMarkers.prototype.initialize.call(this, null, options); - this._baseUrl = baseUrl; - this.on('markerclick', this.showPassDescription, this); - this.on('load', this._onLoad, this); - this.loader = L.Util.ajaxLoader(baseUrl + this.options.filePasses, - this._loadMarkers.bind(this), - {responseType: 'json', timeout: 30000} - ); - }, - - clone: function() { - var options = {}; - L.extend(options, this.options, {iconScale: 1.2, labelFontSize: 12}); - return new westraPasesMarkers(this._baseUrl, options); - }, - - loadData: function() { - this.loader.tryLoad(); - }, - - onAdd: function(map) { - L.Layer.CanvasMarkers.prototype.onAdd.call(this, map); - this.loadData(); - }, - - _onLoad: function() { - if (this._loaded) { - this.fire('rendered'); - } - }, - - _makeTooltip: function(marker) { - var properties = marker.properties, - toolTip = properties.grade || ''; - if (toolTip && properties.elevation) { - toolTip += ', ' - } - toolTip += properties.elevation || ''; - if (toolTip) { - toolTip = ' (' + toolTip + ')'; - } - toolTip = (properties.name || 'без названия') + toolTip; - toolTip = (properties.is_summit ? 'Вершина ' : 'Перевал ') + toolTip; - return toolTip; - }, - - _passToGpx: function(marker) { - var gpx = [], - label = marker.tooltip; - if (typeof label === 'function') { - label = label(marker); - } - label = escapeHtml(label); - gpx.push('<?xml version="1.0" encoding="UTF-8" standalone="no" ?>'); - gpx.push( - '<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="http://nakarte.tk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd">' - ); - gpx.push('<wpt lat="' + marker.latlng.lat.toFixed(6) + '" lon="' + marker.latlng.lng.toFixed(6) + '">'); - gpx.push('<name>'); - gpx.push(label); - gpx.push('</name>'); - gpx.push('</wpt>'); - gpx.push('</gpx>'); - gpx = gpx.join(''); - var filename = marker.properties.name || 'Без названия'; - saveAs(new Blob([gpx], {type: 'application/gpx+xml'}), filename + '.gpx'); - }, - - _passToKml: function(marker) { - var kml = [], - label = marker.tooltip; - if (typeof label === 'function') { - label = label(marker); - } - label = escapeHtml(label); - kml.push('<?xml version="1.0" encoding="UTF-8"?>'); - kml.push('<kml xmlns="http://www.opengis.net/kml/2.2">'); - kml.push('<Placemark>'); - kml.push('<name>'); - kml.push(label); - kml.push('</name>'); - kml.push('<Point>'); - kml.push('<coordinates>'); - kml.push(marker.latlng.lng.toFixed(6) + ',' + marker.latlng.lat.toFixed(6) + ',0'); - kml.push('</coordinates>'); - kml.push('</Point>'); - kml.push('</Placemark>'); - kml.push('</kml>'); - kml = kml.join(''); - var filename = marker.properties.name || 'Без названия'; - saveAs(new Blob([kml], {type: 'application/vnd.google-earth.kml+xml'}), filename + '.kml'); - }, - - _makeIcon: function(marker) { - var className; - className = 'westra-pass-marker '; - if (marker.properties.is_summit) { - className += 'westra-pass-marker-summit'; - } else { - className += 'westra-pass-marker-' + marker.properties.grade_eng; - } - return L.Util.iconFromBackgroundUrl(className); - }, - - _loadMarkers: function(xhr) { - var markers = [], - features = xhr.response, - feature, i, marker, className; - for (i = 0; i < features.length; i++) { - feature = features[i]; - marker = { - latlng: { - lat: feature.latlon[0], - lng: feature.latlon[1], - }, - label: feature.name || "", - icon: this._makeIcon, - tooltip: this._makeTooltip.bind(this), - properties: feature - }; - markers.push(marker); - } - this.addMarkers(markers); - this._loaded = true; - }, - - - showPassDescription: function(e) { - if (!this._map) { - return - } - var properties = e.marker.properties, - latLng = e.marker.latlng, - url, i, comment; - var description = ['<table class="pass-details">']; - description.push('<tr><td>'); - description.push(properties.is_summit ? 'Вершина ' : 'Перевал '); - description.push('</td><td>'); - description.push(properties.name || "название неизвестно"); - description.push('</td></tr>'); - if (properties.altnames) { - description.push('<tr><td>'); - description.push('Другие названия'); - description.push('</td><td>'); - description.push(properties.altnames); - description.push('</td></tr>'); - } - description.push('<tr><td>'); - description.push('Категория'); - description.push('</td><td>'); - description.push(properties.grade || "неизвестная"); - description.push('</td></tr><tr><td>'); - description.push('Высота'); - description.push('</td><td>'); - description.push(properties.elevation ? (properties.elevation + " м") : "неизвестная"); - description.push('</td></tr>'); - if (!properties.is_summit) { - description.push('<tr><td>'); - description.push('Соединяет'); - description.push('</td><td>'); - description.push(properties.connects || "неизвестнo"); - description.push('</td></tr>'); - } - description.push('<tr><td>'); - description.push('Характеристика склонов'); - description.push('</td><td>'); - description.push(properties.slopes || "неизвестная"); - description.push('</td></tr>'); - - description.push('<tr><td>'); - description.push('Координаты'); - description.push('</td><td>'); - description.push('<table class="westra-passes-description-coords">' + - '<tr><td>Широта</td><td>Долгота</td></tr>' + - '<tr><td>' + latLng.lat.toFixed(5) + '</td><td>' + latLng.lng.toFixed(5) + '</td>' + - '<td><a id="westra-pass-gpx" title="Сохранить">gpx</a></td>' + - '<td><a id="westra-pass-kml" title="Сохранить">kml</a></td></tr></table>' - ); - description.push('</td></tr>'); - - description.push('<tr><td>'); - description.push('На сайте Вестры'); - description.push('</td><td>'); - url = 'http://westra.ru/passes/Passes/' + properties.id; - description.push( - '<a id="westra-pass-link" href="' + url + '">' + url + '</a>' - ); - description.push('</td></tr>'); - - description.push('<tr><td>'); - description.push('Добавил'); - description.push('</td><td>'); - description.push(properties.author || "неизвестно"); - description.push('</td></tr>'); - - if (properties.comments) { - description.push('<tr><td>'); - description.push('Комментарии'); - description.push('</td><td>'); - for (i = 0; i < properties.comments.length; i++) { - comment = properties.comments[i]; - description.push('<p class="westra-passes-description-comment">'); - if (comment.user) { - description.push( - '<span class="westra-passes-description-comment-author">' + comment.user + ':</span>' - ); - } - description.push(comment.content + '</p>'); - } - description.push('</td></tr>'); - } - description.push('</table>'); - var popUp = this._map.openPopup(description.join(''), latLng, {maxWidth: 500}); - document.getElementById('westra-pass-link').onclick = function() { - openPopup(url, 650); - return false; - }; - document.getElementById('westra-pass-gpx').onclick = function() { - this._passToGpx(e.marker); - return false; - }.bind(this); - document.getElementById('westra-pass-kml').onclick = function() { - this._passToKml(e.marker); - return false; - }.bind(this); - } - } -); diff --git a/src/lib/leaflet.layer.westraPasses/westraPassesMarkers.js b/src/lib/leaflet.layer.westraPasses/westraPassesMarkers.js @@ -0,0 +1,231 @@ +import L from 'leaflet'; +import 'lib/leaflet.layer.canvasMarkers/canvasMarkers' +import openPopup from 'lib/popupWindow/popupWindow'; +import escapeHtml from 'escape-html'; +import {saveAs} from 'browser-filesaver'; +import iconFromBackgroundImage from 'lib/iconFromBackgroundImage/iconFromBackgroundImage'; +import {prepareXMLHttpRequestPromise} from 'lib/xhr-promise/xhr-promise'; + + +const westraPasesMarkers = L.Layer.CanvasMarkers.extend({ + options: { + filePasses: 'westra_passes.json', + scaleDependent: true + }, + + initialize: function(baseUrl, options) { + L.Layer.CanvasMarkers.prototype.initialize.call(this, null, options); + this.on('markerclick', this.showPassDescription, this); + const {send, promise} = prepareXMLHttpRequestPromise(baseUrl + this.options.filePasses, + {responseType: 'json', timeout: 30000} + ); + promise.then((xhr) => this._loadMarkers(xhr)); + this.sendDataRequest = send; + }, + + loadData: function() { + if (this._downloadStarted) { + return; + } + this._downloadStarted = true; + this.sendDataRequest(); + }, + + onAdd: function(map) { + L.Layer.CanvasMarkers.prototype.onAdd.call(this, map); + this.loadData(); + }, + + _makeTooltip: function(marker) { + var properties = marker.properties, + toolTip = properties.grade || ''; + if (toolTip && properties.elevation) { + toolTip += ', ' + } + toolTip += properties.elevation || ''; + if (toolTip) { + toolTip = ' (' + toolTip + ')'; + } + toolTip = (properties.name || 'без названия') + toolTip; + toolTip = (properties.is_summit ? 'Вершина ' : 'Перевал ') + toolTip; + return toolTip; + }, + + _passToGpx: function(marker) { + let label = marker.tooltip; + if (typeof label === 'function') { + label = label(marker); + } + label = escapeHtml(label); + const gpx = ` + <?xml version="1.0" encoding="UTF-8" standalone="no" ?> + <gpx xmlns="http://www.topografix.com/GPX/1/1" creator="http://nakarte.tk" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd"> + <wpt lat="${marker.latlng.lat.toFixed(6)}" lon="${marker.latlng.lng.toFixed(6)}"> + <name>${label}</name> + </wpt> + </gpx> + `; + var filename = marker.properties.name || 'Без названия'; + saveAs(new Blob([gpx], {type: 'application/gpx+xml'}), filename + '.gpx'); + }, + + _passToKml: function(marker) { + let label = marker.tooltip; + if (typeof label === 'function') { + label = label(marker); + } + label = escapeHtml(label); + const kml = ` + <?xml version="1.0" encoding="UTF-8"?> + <kml xmlns="http://www.opengis.net/kml/2.2"> + <Placemark> + <name>${label}</name> + <Point> + <coordinates> + ${marker.latlng.lng.toFixed(6)},${marker.latlng.lat.toFixed(6)},0 + </coordinates> + </Point> + </Placemark> + </kml> + `; + var filename = marker.properties.name || 'Без названия'; + saveAs(new Blob([kml], {type: 'application/vnd.google-earth.kml+xml'}), filename + '.kml'); + }, + + _makeIcon: function(marker) { + var className; + className = 'westra-pass-marker '; + if (marker.properties.is_summit) { + className += 'westra-pass-marker-summit'; + } else { + className += 'westra-pass-marker-' + marker.properties.grade_eng; + } + return iconFromBackgroundImage(className); + }, + + _loadMarkers: function(xhr) { + var markers = [], + features = xhr.response, + feature, i, marker; + for (i = 0; i < features.length; i++) { + feature = features[i]; + marker = { + latlng: { + lat: feature.latlon[0], + lng: feature.latlon[1] + }, + label: feature.name || "", + icon: this._makeIcon, + tooltip: this._makeTooltip.bind(this), + properties: feature + }; + markers.push(marker); + } + this.addMarkers(markers); + }, + + + showPassDescription: function(e) { + if (!this._map) { + return; + } + const properties = e.marker.properties, + latLng = e.marker.latlng, + url = 'http://westra.ru/passes/Passes/' + properties.id; + let altnames = '', connects = '', comments = ''; + if (properties.altnames) { + altnames = ` + <tr> + <td>Другие названия</td> + <td>${properties.altnames}</td> + </tr>`; + } + + if (!properties.is_summit) { + connects = ` + <tr> + <td>Соединяет</td> + <td>${properties.connects || "неизвестнo"}</td> + </tr>`; + } + + if (properties.comments) { + + for (let comment of properties.comments) { + let user = ''; + if (comment.user) { + user = `<span class="westra-passes-description-comment-author">${comment.user}:</span>` + } + comments += `<p class="westra-passes-description-comment">${user}${comment.content}</p>`; + } + comments = ` + <tr> + <td>Комментарии</td> + <td>${comments}</td> + </tr>` + } + + let description = ` + <table class="pass-details"> + <tr> + <td>${properties.is_summit ? 'Вершина ' : 'Перевал '}</td> + <td>${properties.name || 'название неизвестно'}</td> + </tr> + ${altnames} + <tr> + <td>Категория</td> + <td>${properties.grade || "неизвестная"}</td> + </tr> + <tr> + <td>Высота</td> + <td>${properties.elevation ? (properties.elevation + ' м') : 'неизвестная'}</td> + </tr> + ${connects} + <tr> + <td>Характеристика склонов</td> + <td>${properties.slopes || "неизвестная"}</td> + </tr> + <tr> + <td>Координаты</td> + <td> + <table class="westra-passes-description-coords"> + <tr> + <td>Широта</td> + <td>Долгота</td> + </tr> + <tr> + <td>${latLng.lat.toFixed(5)}</td> + <td>${latLng.lng.toFixed(5)}</td> + <td><a id="westra-pass-gpx" title="Сохранить">gpx</a></td> + <td><a id="westra-pass-kml" title="Сохранить">kml</a></td> + </tr> + </table> + </td> + </tr> + <tr> + <td>На сайте Вестры</td> + <td><a id="westra-pass-link" href="${url}">${url}</a></td></tr> + <tr> + <td>Добавил</td> + <td>${properties.author || "неизвестно"}</td> + </tr> + ${comments} + </table>`; + this._map.openPopup(description, latLng, {maxWidth: 500}); + document.getElementById('westra-pass-link').onclick = function() { + openPopup(url, 650); + return false; + }; + document.getElementById('westra-pass-gpx').onclick = function() { + this._passToGpx(e.marker); + return false; + }.bind(this); + document.getElementById('westra-pass-kml').onclick = function() { + this._passToKml(e.marker); + return false; + }.bind(this); + } + } +); + +export default westraPasesMarkers; +\ No newline at end of file diff --git a/src/lib/xhr-promise/xhr-promise.js b/src/lib/xhr-promise/xhr-promise.js @@ -1,19 +1,22 @@ -export default function XMLHttpRequestPromise(url, method='GET', data=null, responseType='', timeout=0) { +function makeRequest(url, {method='GET', data=null, responseType='', timeout=0} = {}) { const xhr = new XMLHttpRequest(); xhr.open(method, url); xhr.responseType = responseType; xhr.timeout = timeout; - const result = new Promise(function(resolve, reject) { + const promise = new Promise(function(resolve, reject) { xhr.onreadystatechange = function() { if (xhr.readyState === 4) { - if (xhr.status) { - resolve(xhr); - } else { - reject(xhr); - } + resolve(xhr); } } }); + return {xhr, promise, send: xhr.send.bind(xhr)}; +} + +function XMLHttpRequestPromise(url, {method='GET', data=null, responseType='', timeout=0} = {}) { + const {xhr, promise} = makeRequest(url, {method, data, responseType, timeout}); xhr.send(); - return result; + return promise; } + +export {XMLHttpRequestPromise, makeRequest as prepareXMLHttpRequestPromise}; +\ No newline at end of file