commit bc6da9b9542ac4b3354716d3927d5a05f8f3df03
parent 74cc415a621057fc6f37418b306b2fa32b07c94b
Author: Sergej Orlov <wladimirych@gmail.com>
Date: Tue, 22 Nov 2016 11:01:11 +0300
[westra passes] code refactoring
Diffstat:
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