nakarte

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

commit a7baa1ece211d6cea6ef66e9487f4d5fb77285b5
parent 215419bbf798bb54ead65d18123f45ec5671584a
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Mon, 21 Nov 2016 22:19:03 +0300

added westra passes layer

Diffstat:
Mpackage.json | 3+++
Msrc/config.js | 3++-
Msrc/layers.js | 3++-
Asrc/lib/leaflet.layer.canvasMarkers/canvasMarkers.css | 18++++++++++++++++++
Asrc/lib/leaflet.layer.canvasMarkers/canvasMarkers.js | 464+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.layer.westraPasses/pass-1a.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-1b.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-2a.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-2b.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-3a.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-3b.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-nograde.png | 0
Asrc/lib/leaflet.layer.westraPasses/pass-unknown-notconfirmed.png | 0
Asrc/lib/leaflet.layer.westraPasses/summit.png | 0
Asrc/lib/leaflet.layer.westraPasses/westraPasses.css | 155+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/leaflet.layer.westraPasses/westraPasses.js | 417+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/lib/popupWindow/popupWindow.js | 28++++++++++++++++++++++++++++
17 files changed, 1089 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json @@ -49,9 +49,12 @@ "whatwg-fetch": "1.0.0" }, "dependencies": { + "browser-filesaver": "^1.1.1", + "escape-html": "^1.0.3", "knockout": "^3.4.0", "leaflet": "^1.0.1", "load-script": "^1.0.0", + "rbush": "^2.0.1" "raw-loader": "^0.5.1", "react": "^15.3.2", "react-dom": "^15.3.2" diff --git a/src/config.js b/src/config.js @@ -1,5 +1,6 @@ export default { email: 'nakarte@nakarte.tk', googleApiUrl: 'https://maps.googleapis.com/maps/api/js?v=3', - bingKey: 'AhZy06XFi8uAADPQvWNyVseFx4NHYAOH-7OTMKDPctGtYo86kMfx2T0zUrF5AAaM' + bingKey: 'AhZy06XFi8uAADPQvWNyVseFx4NHYAOH-7OTMKDPctGtYo86kMfx2T0zUrF5AAaM', + westraDataBaseUrl: 'http://nakarte.tk/westraPasses/' } diff --git a/src/layers.js b/src/layers.js @@ -4,6 +4,7 @@ import 'lib/leaflet.layer.google/google'; import 'lib/leaflet.layer.bing/bing'; import config from './config'; import 'lib/leaflet.layer.soviet-topomaps-grid/soviet-topomaps-grid'; +import 'lib/leaflet.layer.westraPasses/westraPasses'; function getBaseMaps() { return { @@ -56,7 +57,7 @@ function getOverlays() { "Soviet topo maps grid": new L.Layer.SovietTopoGrid({code: 'Ng'}), // "Wikimapia": new L.Wikimapia({code: 'W', zIndexOffset: 10000}), // "Google Street View": new L.GoogleStreetView('street-view', {print: true, code: 'Gs', zIndexOffset: 10000}), - // "Mountain passes (Westra)": new L.WestraPasses('/westraPasses/', {code: 'Wp', print: true, zIndexOffset: 10000}) + "Mountain passes (Westra)": new L.Layer.WestraPasses(config.westraDataBaseUrl, {code: 'Wp', print: true}) }; } diff --git a/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.css b/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.css @@ -0,0 +1,18 @@ +.canvas-marker-tooltip { + visibility: hidden; + margin-left: -50%; + margin-right: 50%; + white-space: nowrap; + background-color: white; + border: 1px solid #333; + border-radius: 3px; + margin-top: -30px; + opacity: 1; + padding: 0 2px; + transition-property: visibility; +} + +.canvas-marker-tooltip-on { + visibility: visible; + transition-delay: 0.2s; +} diff --git a/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.js b/src/lib/leaflet.layer.canvasMarkers/canvasMarkers.js @@ -0,0 +1,464 @@ +import L from 'leaflet'; +import './canvasMarkers.css'; +import rbush from 'rbush'; + +/* + Marker definition: + { + latlng: L.Latlng, + icon: {url: string, center: [x, y]} or function(marker) returning icon, + label: sting 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); + } + return cache[arg]; + } +} + +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, + labelFontName: 'Verdana, Arial, sans-serif', + labelFontSize: 10, + iconScale: 1, + pane: 'rasterMarker' + }, + + 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._iconPositions = {}; + this._labelPositions = {}; + this._zoom = null; + this.addMarkers(markers); + this._images = {}; + this._tileQueue = []; + this._hoverMarker = null; + this.on('markerenter', this.onMarkerEnter, this); + this.on('markerleave', this.onMarkerLeave, this); + }, + + addMarkers: function(markers) { + if (markers) { + this.rtree.load(markers); + this.resetLabels(); + setTimeout(this.redraw.bind(this), 0); + } + }, + + addMarker: function(marker) { + // FIXME: adding existing marker must be be noop + this.rtree.insert(marker); + this.resetLabels(); + setTimeout(this.redraw.bind(this), 0); + }, + + removeMarker: function(marker) { + this.removeMarkers([marker]); + }, + + removeMarkers: function(markers) { + var i, marker, markerId; + for (i = 0; i < markers.length; i++) { + marker = markers[i]; + this.rtree.remove(marker); + } + this.resetLabels(); + setTimeout(this.redraw.bind(this), 0); + }, + + updateMarkers: function(markers) { + var i; + this.removeMarkers(markers); + this.addMarkers(markers); + }, + + updateMarker: function(marker) { + this.updateMarkers([marker]); + }, + + setMarkerPosition: function(marker, latlng) { + this.removeMarker(marker); + marker.latlng = latlng; + this.addMarker(marker); + }, + + getMarkers: function() { + return this.rtree.all(); + }, + + findLabelPosition: function(iconCenter, iconSize, textWidth, textHeight) { + var verticalPadding = 0, + xPositions = [iconCenter[0] + iconSize[0] / 2 + 2, iconCenter[0] - iconSize[0] / 2 - textWidth - 2], + 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]); + if (intersectionSum < minIntersectionSum) { + minIntersectionSum = intersectionSum; + bestX = x; + bestY = y; + if (intersectionSum === 0) { + break; + } + } + if (intersectionSum === 0) { + break; + } + } + } + + 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(); + } + }; + img.src = url; + } + } + if (self._iconPreloadFinished()) { + self._processTilesQueue(); + } + }, + + createTile: function(coords) { + const canvas = L.DomUtil.create('canvas', 'leaflet-tile'); + canvas.width = this.options.tileSize; + canvas.height = this.options.tileSize; + this.drawTile(canvas, coords, coords.z); + return canvas; + }, + + drawTile: function(canvas, tilePoint, zoom) { + var tileSize = this.options.tileSize, + tileN = tilePoint.y * tileSize, + tileW = tilePoint.x * tileSize, + tileS = tileN + tileSize, + tileE = tileW + tileSize, + + iconsHorPad = 520, + iconsVertPad = 50, + labelsHorPad = 256, + labelsVertPad = 20, + 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), + this._map.unproject([tileE + labelsHorPad, tileN - labelsVertPad], zoom) + ), + iconUrls = [], + markerJobs = {}, + marker, p, icon, markerId, img; + + var markers = this.rtree.search( + { + minX: iconsBounds.getWest(), + minY: iconsBounds.getSouth(), + maxX: iconsBounds.getEast(), + maxY: iconsBounds.getNorth() + } + ); + + for (var i = 0; i < markers.length; i++) { + marker = markers[i]; + p = this._map.project(marker.latlng, zoom); + icon = marker.icon; + if (typeof icon === 'function') { + icon = icon(marker); + } + iconUrls.push(icon.url); + markerId = L.stamp(marker); + markerJobs[markerId] = {marker: marker, icon: icon, projectedXY: p}; + } + var self = this; + this.preloadIcons(iconUrls, function() { + if (!self._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(); + } + ctx = canvas.getContext('2d'); + ctx.font = L.Util.template('bold {size}px {name}', + {'name': self.options.labelFontName, 'size': self.options.labelFontSize} + ); + for (markerId in markerJobs) { + job = markerJobs[markerId]; + img = self._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; + 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]); + } + p = self._iconPositions[markerId]; + x = p[0]; + y = p[1]; + 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; + 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]); + } + } else { + self._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) { + //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; + ctx.textBaseline = 'bottom'; + ctx.shadowColor = '#fff'; + ctx.strokeStyle = '#fff'; + ctx.fillStyle = '#000'; + ctx.lineWidth = 1; + ctx.shadowBlur = 2; + ctx.strokeText(job.label, x, y + textHeight); + ctx.shadowBlur = 0; + 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; + ctx.drawImage(job.img, x, y, job.iconSize[0], job.iconSize[1]); + } + } + // setTimeout(() => callback(canvas), 0); + } + ); + return this; + }, + + resetLabels: function() { + this._iconPositions = {}; + this._labelPositions = {}; + this._regions.clear(); + }, + + findMarkerFromMouseEvent: function(e) { + var p = this._map.project(e.latlng), + region = this._regions.search({minX: p.x, minY: p.y, maxX: p.x, maxY: p.y})[0], + marker; + if (region) { + marker = region[4]; + } else { + marker = null; + } + return marker; + }, + + onMouseMove: function(e) { + var marker = this.findMarkerFromMouseEvent(e); + if (this._hoverMarker !== marker) { + if (this._hoverMarker) { + this.fire('markerleave', {marker: this._hoverMarker}); + } + if (marker) { + this.fire('markerenter', {marker: marker}); + } + this._hoverMarker = marker; + } + }, + + showTooltip: function(e) { + var text; + if (!e.marker.tooltip) { + return; + } + text = e.marker.tooltip; + if (typeof text === 'function') { + text = text(e.marker); + if (!e.marker.tooltip) { + return; + } + } + this.toolTip.innerHTML = text; + var p = this._map.latLngToLayerPoint(e.marker.latlng); + L.DomUtil.setPosition(this.toolTip, p); + L.DomUtil.addClass(this.toolTip, 'canvas-marker-tooltip-on'); + }, + + onMarkerEnter: function(e) { + this._map._container.style.cursor = 'pointer'; + this.showTooltip(e); + + }, + + onMarkerLeave: function() { + this._map._container.style.cursor = ''; + L.DomUtil.removeClass(this.toolTip, 'canvas-marker-tooltip-on'); + }, + + onMouseOut: function() { + if (this._hoverMarker) { + this._hoverMarker = null; + this.fire('markerleave', {marker: this._hoverMarker}); + } + }, + + onClick: function(e) { + var marker = this.findMarkerFromMouseEvent(e); + if (marker) { + L.extend(e, {marker: marker}); + this.fire('markerclick', e); + } + }, + + onRightClick: function(e) { + var marker = this.findMarkerFromMouseEvent(e); + if (marker) { + L.extend(e, {marker: marker}); + this.fire('markercontextmenu', e); + } + }, + + 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); + map.on('click', this.onClick, this); + map.on('contextmenu', this.onRightClick, this); + this.toolTip = L.DomUtil.create('div', 'canvas-marker-tooltip', this._map.getPanes().markerPane); + }, + + onRemove: function(map) { + this._map.off('mousemove', this.onMouseMove, this); + this._map.off('mouseout', this.onMouseOut, this); + this._map.off('click', this.onClick, this); + this._map.off('contextmenu', this.onRightClick, this); + if (this._hoverMarker) { + this._hoverMarker = null; + this.fire('markerleave', {marker: this._hoverMarker}) + } + this._map.getPanes().markerPane.removeChild(this.toolTip); + L.GridLayer.prototype.onRemove.call(this, map); + } + } +); diff --git a/src/lib/leaflet.layer.westraPasses/pass-1a.png b/src/lib/leaflet.layer.westraPasses/pass-1a.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-1b.png b/src/lib/leaflet.layer.westraPasses/pass-1b.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-2a.png b/src/lib/leaflet.layer.westraPasses/pass-2a.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-2b.png b/src/lib/leaflet.layer.westraPasses/pass-2b.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-3a.png b/src/lib/leaflet.layer.westraPasses/pass-3a.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-3b.png b/src/lib/leaflet.layer.westraPasses/pass-3b.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-nograde.png b/src/lib/leaflet.layer.westraPasses/pass-nograde.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/pass-unknown-notconfirmed.png b/src/lib/leaflet.layer.westraPasses/pass-unknown-notconfirmed.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/summit.png b/src/lib/leaflet.layer.westraPasses/summit.png Binary files differ. diff --git a/src/lib/leaflet.layer.westraPasses/westraPasses.css b/src/lib/leaflet.layer.westraPasses/westraPasses.css @@ -0,0 +1,155 @@ +.westra-pass-tooltip { + visibility: hidden; + position: absolute; + z-index: 1000000; + top: -30px; + left: 0; + transition-property: visibility; + transition-delay: 0.2s; +} + +.westra-pass-tooltip div { + margin-left: -50%; + margin-right: 50%; + white-space: nowrap; + background-color: white; + border: 1px solid #333; + border-radius: 3px; + margin-top: -1px; + opacity: 1; + padding: 0 2px; +} + + +.westra-pass-marker { + width: 14px; + height: 14px; + margin-left: -7px; + margin-top: -7px; + background-repeat: no-repeat; +} + +.westra-pass-marker-summit { + background-image: url("summit.png"); +} + +.westra-pass-marker-1a { + background-image: url("pass-1a.png"); +} + +.westra-pass-marker-1b { + background-image: url("pass-1b.png"); +} + +.westra-pass-marker-2a { + background-image: url("pass-2a.png"); +} + +.westra-pass-marker-2b { + background-image: url("pass-2b.png"); +} + +.westra-pass-marker-3a { + background-image: url("pass-3a.png"); +} + +.westra-pass-marker-3b { + background-image: url("pass-3b.png"); +} + +.westra-pass-marker-unknown { + background-image: url("pass-unknown-notconfirmed.png"); +} + +.westra-pass-marker-nograde { + background-image: url("pass-nograde.png"); +} + +.westra-region-label { + white-space: nowrap; + width: auto !important; + height: auto !important; +} + +.westra-region-label span { + margin-left: -50%; + color: #c55; + font-family: sans-serif; + font-size: 14px; + /*font-weight: bold;*/ + text-shadow: 1px 0 0 #eee, + 1px 1px 0 #eee, + 0 1px 0 #eee, + -1px 1px 0 #eee, + -1px 0 0 #eee, + -1px -1px 0 #eee, + 0 -1px 0 #eee, + 1px -1px 0 #eee; + +} + +.westra-region-polygon { + stroke-width: 2; + opacity: 0.5; + fill-opacity: 0.7; + stroke: #c55; + fill: #c55 +} + +.pass-details { + border-collapse: collapse; +} + +.pass-details td { + border-bottom: 1px solid #ccc; + padding: 1mm 0; +} + +.pass-details td:last-child { + padding-left: 1em; +} + +.pass-details td:first-child { + /*white-space: nowrap;*/ +} + +.pass-details tr:last-child td { + border: none; +} + +.westra-passes-description-coords { + border-collapse: collapse; +} + +.pass-details .westra-passes-description-coords td { + border: none; + padding: 0 0 0 1em; +} + + +.pass-details .westra-passes-description-coords td:first-child { + padding: 0; +} + +.westra-passes-description-coords a { + color: #000; + border-bottom: 1px dashed #000; + text-decoration: none; + cursor: pointer; +} + +.westra-passes-description-coords tr:first-child td { + color: #999; + font-size: 9px; +} + + +.westra-passes-description-comment { + margin: 0 !important; +} + +.westra-passes-description-comment-author { + font-weight: bold; + color: #777; + margin-right: 1em !important; +} diff --git a/src/lib/leaflet.layer.westraPasses/westraPasses.js b/src/lib/leaflet.layer.westraPasses/westraPasses.js @@ -0,0 +1,417 @@ +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'); + } + } +); + + +L.Layer.WestraPasses = L.Layer.extend({ + options: { + fileRegions1: 'westra_regions_geo1.json', + fileRegions2: 'westra_regions_geo2.json', + scaleDependent: true + }, + + initialize: function(baseUrl, options) { + L.setOptions(this, options); + this.markers = new westraPasesMarkers(baseUrl); + this.regions1 = new L.GeoJSONAjax(baseUrl + this.options.fileRegions1, { + className: 'westra-region-polygon', + onEachFeature: this._setRegionLabel.bind(this, 'regions1') + } + ); + this.regions2 = new L.GeoJSONAjax(baseUrl + this.options.fileRegions2, { + className: 'westra-region-polygon', + onEachFeature: this._setRegionLabel.bind(this, 'regions2') + } + ); + }, + _setRegionLabel: function(layerName, feature, layer) { + var latlon = layer.getBounds().getCenter(); + var icon = L.divIcon({ + className: 'westra-region-label', + html: '<span>' + feature.properties.name + '</span>' + } + ); + var labelMarker = L.marker(latlon, {icon: icon}); + this[layerName].addLayer(labelMarker); + function zoomToRegion() { + this._map.fitBounds(layer.getBounds()); + } + + layer.on('click', zoomToRegion, this); + labelMarker.on('click', zoomToRegion, this); + }, + + // setZIndex: function(z) { + // this.markers.setZIndex(z + this.options.zIndexOffset || 0); + // }, + + setLayersVisibility: function(e) { + if (!this._map) { + return; + } + var newZoom; + var zoomFinished = e ? (e.type != 'zoomanim') : true; + if (e && e.zoom !== undefined) { + newZoom = e.zoom; + } else { + newZoom = this._map.getZoom(); + } + if (newZoom < 2) { + this._map.removeLayer(this.markers); + this._map.removeLayer(this.regions1); + this._map.removeLayer(this.regions2); + } else if (newZoom < 7) { + this._map.removeLayer(this.markers); + this._map.addLayer(this.regions1); + this._map.removeLayer(this.regions2); + } + else if (newZoom < 10) { + this._map.removeLayer(this.regions1); + this._map.addLayer(this.regions2); + this._map.removeLayer(this.markers); + } else { + if (zoomFinished) { + this._map.addLayer(this.markers); + } + this._map.removeLayer(this.regions1); + this._map.removeLayer(this.regions2); + } + }, + + onAdd: function(map) { + this._map = map; + this.regions1.loadData(); + this.regions2.loadData(); + this.markers.loadData(); + this.setLayersVisibility(); + map.on('zoomend', this.setLayersVisibility, this); + map.on('zoomanim', this.setLayersVisibility, this); + }, + + onRemove: function() { + this._map.removeLayer(this.markers); + this._map.removeLayer(this.regions1); + this._map.removeLayer(this.regions2); + 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/popupWindow/popupWindow.js b/src/lib/popupWindow/popupWindow.js @@ -0,0 +1,28 @@ +function openPopup (url, width) { + var left, top, height, + screenLeft = screen.availLeft || 0, + screenTop = screen.availTop || 0, + bordersWidth = 8; + // if browser window is in the right half of screen, place new window on left half + if (window.screenX - screenLeft - bordersWidth * 1.5 > width) { + left = window.screenX - width - bordersWidth * 1.5; + // if browser window is in the left half of screen, place new window on right half + } else if (window.screenX + window.outerWidth + width + bordersWidth * 1.5 < screenLeft + screen.availWidth) { + left = window.screenX + window.outerWidth + bordersWidth; + // if screen is small or browser window occupies whole screen, place new window on top of current window + } else { + left = window.screenX + window.outerWidth / 2 - width / 2; + if (left < 0) { + left = 0; + } + } + top = window.screenY; + height = window.innerHeight; + var features = 'width=' + width + ',height=' + height + ',left=' + left + ',top=' + top; + features += ',resizable,scrollbars'; + // to open single instance replace null with some string + window.open(url, null, features) + .focus(); +} + +export default openPopup;