diff --git a/README.md b/README.md
index 67177fe..64551da 100644
--- a/README.md
+++ b/README.md
@@ -6,17 +6,17 @@
## Features
-* Serves tiles bitmap tiles usable in Leaflet or OpenLayers
-* Serves vector tiles rendered on clientside by KothicJS
-* Uses KothicJS both as bitmap renderer on serverside and canvas renderer on clientside
-* Filesystem caching mechanisms
-* Map styling with MapCSS
-* Support for tiles in multiple rendering styles
-* Designed to use a osm2pgsql hstore database containing OpenStreetMap data
-* Refresh tiles manually by GET requests
-* Rerender expired tiles automatically in the background
-* High performance that profits from the non-blocking I/O design of NodeJS
-* Easy to install on several operating systems, distributions and environments due to less dependencies
+ * Serves tiles bitmap tiles usable in Leaflet or OpenLayers
+ * Serves vector tiles rendered on clientside by KothicJS
+ * Uses KothicJS both as bitmap renderer on serverside and canvas renderer on clientside
+ * Filesystem caching mechanisms
+ * Map styling with MapCSS
+ * Support for tiles in multiple rendering styles
+ * Designed to use a osm2pgsql hstore database containing OpenStreetMap data
+ * Refresh tiles manually by GET requests
+ * Rerender expired tiles automatically in the background
+ * High performance that profits from the non-blocking I/O design of NodeJS
+ * Easy to install on several operating systems, distributions and environments due to less dependencies
## Authors
@@ -41,7 +41,7 @@
After that you can install all necessary NodeJS modules with npm:
- $ npm install canvas@1.0.4
+ $ npm install canvas
$ npm install rbush
$ npm install mkdirp
$ npm install pg
@@ -122,20 +122,6 @@
Have a look at an [example toolchain](https://github.com/rurseekatze/OpenRailwayMap/blob/master/import/import.sh) for an example of using osm2pgsql with filtered data.
- If you want to use vector tiles for client-side rendering, you have to install KothicJS and do some modifications. If you just want to use bitmap tiles, you can skip the next steps.
-
- Clone the KothicJS repository:
-
- $ git clone https://github.com/kothic/kothic-js.git
- $ cd kothic-js
-
- Apply some patches, otherwise some features will not work properly:
-
- $ patch src/kothic.js < ../patches/kothic.diff
- $ patch src/style/style.js < ../patches/style.diff
- $ patch dist/kothic-leaflet.js < ../patches/kothic-leaflet.diff
- $ patch style/mapcss.js < ../patches/mapcss.diff
-
You need MapCSS converter to compile your MapCSS styles to javascript:
$ wget https://raw2.github.com/kothic/kothic-js-mapcss/master/scripts/mapcss_converter.py
@@ -151,6 +137,8 @@
$ for stylefile in *.mapcss ; do python mapcss_converter.py --mapcss "$stylefile" --icons-path . ; done
+ Note that you have to recompile the stylesheets every time you change the MapCSS files to apply the changes. It is also necessary to restart the tileserver to reload the stylesheets.
+
You need a proxy that routes incoming requests. It is recommended to use a NodeJS proxy like [this](https://github.com/rurseekatze/OpenRailwayMap/blob/master/proxy.js), especially if you are running another webserver like Apache parallel to NodeJS. Remember to change the domains in the script and the configuration of your parallel running webservers. The NodeJS proxy listens on port 80 while parallel webservers should listen on 8080.
Now you are almost ready to run the tileserver. You just need to check the configuration.
@@ -171,7 +159,7 @@ You can set various options to configure your tileserver:
* `expiredtilesdir` Relative or absolute path to the list of expired tiles. _Default: `../../olm/import`_
- * `scriptdir` Relative or absolute path to the directory of the required scripts, usually `kothic-js/src`. _Default: `../js`_
+ * `scriptdir` Relative or absolute path to the directory of the required scripts, usually the `kothic` directory included in this repository. _Default: `../js`_
* `styledir` Relative or absolute path to the directory containing (compiled) MapCSS styles. _Default: `../styles`_
@@ -260,7 +248,7 @@ __Note:__ For some parameters it is also necessary to change the modify the opti
__Leaflet example:__
- Include all javascript files from kothic-js/src and kothic-js/dist and your compiled MapCSS styles into your website.
+ Include all javascript files from kothic/src and kothic/dist and your compiled MapCSS styles into your website.
...
map = L.map('mapFrame');
@@ -359,3 +347,5 @@ This program is free software: you can redistribute it and/or modify it under th
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
+
+__The files in the `kothic` directory are published under other licenses. See the header of each file for more information.__
diff --git a/kothic/kothic-leaflet.js b/kothic/kothic-leaflet.js
new file mode 100644
index 0000000..026bc85
--- /dev/null
+++ b/kothic/kothic-leaflet.js
@@ -0,0 +1,170 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+L.TileLayer.Kothic = L.TileLayer.Canvas.extend({
+ options: {
+ tileSize: 256,
+ zoomOffset: 0,
+ minZoom: 2,
+ maxZoom: 19,
+ updateWhenIdle: true,
+ unloadInvisibleTiles: true,
+ attribution: 'Map data © 2013 OpenStreetMap contributors,' +
+ ' Rendering by Kothic JS',
+ async: true,
+ buffered: false,
+ styles: MapCSS.availableStyles
+ },
+
+ initialize: function(url,options) {
+ L.Util.setOptions(this, options);
+
+ this._url = url;
+ this._canvases = {};
+ this._scripts = {};
+ this._debugMessages = [];
+
+ window.onKothicDataResponse = L.Util.bind(this._onKothicDataResponse, this);
+ },
+
+ _onKothicDataResponse: function(data, zoom, x, y) {
+ var key = [zoom, x, y].join('/'),
+ canvas = this._canvases[key],
+ zoomOffset = this.options.zoomOffset,
+ layer = this;
+
+ if (!canvas) {
+ return;
+ }
+
+ function onRenderComplete() {
+ layer.tileDrawn(canvas);
+
+ document.getElementsByTagName('head')[0].removeChild(layer._scripts[key]);
+ delete layer._scripts[key];
+ }
+
+ var styles = this.options.styles;
+
+ Kothic.render(canvas, data, zoom + zoomOffset, {
+ styles: styles,
+ onRenderComplete: onRenderComplete
+ });
+
+ delete this._canvases[key];
+ },
+
+ getDebugMessages: function() {
+ return this._debugMessages;
+ },
+
+ drawTile: function(canvas, tilePoint, zoom) {
+ var zoomOffset = this.options.zoomOffset,
+ key = [(zoom - zoomOffset), tilePoint.x, tilePoint.y].join('/'),
+ url=this._url.replace('{x}',tilePoint.x).
+ replace('{y}',tilePoint.y).
+ replace('{z}',zoom-zoomOffset);
+ this._canvases[key] = canvas;
+ this._scripts[key] = this._loadScript(url);
+ },
+
+ enableStyle: function(name) {
+ // start modified by rurseekatze
+ if (this.options.styles.indexOf(name) == -1) {
+ // end modified by rurseekatze
+ this.options.styles.push(name);
+ }
+ },
+
+ disableStyle: function(name) {
+ if (this.options.styles.indexOf(name) >= 0) {
+ var i = this.options.styles.indexOf(name);
+ this.options.styles.splice(i, 1);
+ }
+ },
+
+ // start modified by rurseekatze
+ redraw: function() {
+ MapCSS.invalidateCache();
+
+ this._map.getPanes().tilePane.empty = false;
+
+ if (this._map) {
+ this._reset({hard: true});
+ this._update();
+ }
+
+ for (var i in this._tiles) {
+ this._redrawTile(this._tiles[i]);
+ }
+ return this;
+ },
+ // end modified by rurseekatze
+
+ _invertYAxe: function(data) {
+ var type, coordinates, tileSize = data.granularity, i, j, k, l, feature;
+ for (i = 0; i < data.features.length; i++) {
+ feature = data.features[i];
+ coordinates = feature.coordinates;
+ type = data.features[i].type;
+ if (type === 'Point') {
+ coordinates[1] = tileSize - coordinates[1];
+ } else if (type === 'MultiPoint' || type === 'LineString') {
+ for (j = 0; j < coordinates.length; j++) {
+ coordinates[j][1] = tileSize - coordinates[j][1];
+ }
+ } else if (type === 'MultiLineString' || type === 'Polygon') {
+ for (k = 0; k < coordinates.length; k++) {
+ for (j = 0; j < coordinates[k].length; j++) {
+ coordinates[k][j][1] = tileSize - coordinates[k][j][1];
+ }
+ }
+ } else if (type === 'MultiPolygon') {
+ for (l = 0; l < coordinates.length; l++) {
+ for (k = 0; k < coordinates[l].length; k++) {
+ for (j = 0; j < coordinates[l][k].length; j++) {
+ coordinates[l][k][j][1] = tileSize - coordinates[l][k][j][1];
+ }
+ }
+ }
+ } else {
+ throw "Unexpected GeoJSON type: " + type;
+ }
+
+ if (feature.hasOwnProperty('reprpoint')) {
+ feature.reprpoint[1] = tileSize - feature.reprpoint[1];
+ }
+ }
+ },
+
+ _loadScript: function(url) {
+ var script = document.createElement('script');
+ script.src = url;
+ script.charset = 'utf-8';
+ document.getElementsByTagName('head')[0].appendChild(script);
+ return script;
+ }
+});
diff --git a/kothic/kothic.js b/kothic/kothic.js
new file mode 100644
index 0000000..b936a03
--- /dev/null
+++ b/kothic/kothic.js
@@ -0,0 +1,257 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+var Kothic = {
+
+ render: function (canvas, data, zoom, options) {
+
+ if (typeof canvas === 'string') {
+ canvas = document.getElementById(canvas);
+ }
+
+ var styles = (options && options.styles) || [];
+
+ MapCSS.locales = (options && options.locales) || [];
+
+ var devicePixelRatio = Math.max(window.devicePixelRatio || 1, 2);
+
+ var width = canvas.width,
+ height = canvas.height;
+
+ if (devicePixelRatio !== 1) {
+ canvas.style.width = width + 'px';
+ canvas.style.height = height + 'px';
+ canvas.width = canvas.width * devicePixelRatio;
+ canvas.height = canvas.height * devicePixelRatio;
+ }
+
+ var ctx = canvas.getContext('2d');
+ ctx.scale(devicePixelRatio, devicePixelRatio);
+
+ var granularity = data.granularity,
+ ws = width / granularity, hs = height / granularity,
+ collisionBuffer = new Kothic.CollisionBuffer(height, width);
+
+ //console.time('styles');
+
+ // setup layer styles
+ var layers = Kothic.style.populateLayers(data.features, zoom, styles),
+ layerIds = Kothic.getLayerIds(layers);
+
+ // render the map
+ Kothic.style.setStyles(ctx, Kothic.style.defaultCanvasStyles);
+
+ //console.timeEnd('styles');
+
+ Kothic.getFrame(function () {
+ //console.time('geometry');
+
+ Kothic._renderBackground(ctx, width, height, zoom, styles);
+ Kothic._renderGeometryFeatures(layerIds, layers, ctx, ws, hs, granularity);
+
+ if (options && options.onRenderComplete) {
+ options.onRenderComplete();
+ }
+
+ //console.timeEnd('geometry');
+
+ Kothic.getFrame(function () {
+ //console.time('text/icons');
+ Kothic._renderTextAndIcons(layerIds, layers, ctx, ws, hs, collisionBuffer);
+ //console.timeEnd('text/icons');
+
+ //Kothic._renderCollisions(ctx, collisionBuffer.buffer.data);
+ });
+ });
+ },
+
+ _renderCollisions: function (ctx, node) {
+ var i, len, a;
+ if (node.leaf) {
+ for (i = 0, len = node.children.length; i < len; i++) {
+ ctx.strokeStyle = 'red';
+ ctx.lineWidth = 1;
+ a = node.children[i];
+ ctx.strokeRect(Math.round(a[0]), Math.round(a[1]), Math.round(a[2] - a[0]), Math.round(a[3] - a[1]));
+ }
+ } else {
+ for (i = 0, len = node.children.length; i < len; i++) {
+ this._renderCollisions(ctx, node.children[i]);
+ }
+ }
+ },
+
+ getLayerIds: function (layers) {
+ return Object.keys(layers).sort(function (a, b) {
+ return parseInt(a, 10) - parseInt(b, 10);
+ });
+ },
+
+ getFrame: function (fn) {
+ var reqFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
+
+ reqFrame.call(window, fn);
+ },
+
+ _renderBackground: function (ctx, width, height, zoom, styles) {
+ var style = MapCSS.restyle(styles, {}, {}, zoom, 'canvas', 'canvas');
+
+ var fillRect = function () {
+ ctx.fillRect(-1, -1, width + 1, height + 1);
+ };
+
+ for (var i in style) {
+ Kothic.polygon.fill(ctx, style[i], fillRect);
+ }
+ },
+
+ _renderGeometryFeatures: function (layerIds, layers, ctx, ws, hs, granularity) {
+ var layersToRender = {},
+ i, j, len, features, style, queue, bgQueue;
+
+ // polygons
+ for (i = 0; i < layerIds.length; i++) {
+ features = layers[layerIds[i]];
+
+ bgQueue = layersToRender._bg = layersToRender._bg || {};
+ queue = layersToRender[layerIds[i]] = layersToRender[layerIds[i]] || {};
+
+ for (j = 0, len = features.length; j < len; j++) {
+ style = features[j].style;
+
+ if ('fill-color' in style || 'fill-image' in style) {
+ if (style['fill-position'] === 'background') {
+ bgQueue.polygons = bgQueue.polygons || [];
+ bgQueue.polygons.push(features[j]);
+ } else {
+ queue.polygons = queue.polygons || [];
+ queue.polygons.push(features[j]);
+ }
+ }
+ }
+ }
+
+ // casings
+ for (i = 0; i < layerIds.length; i++) {
+ features = layers[layerIds[i]];
+ queue = layersToRender[layerIds[i]] = layersToRender[layerIds[i]] || {};
+
+ for (j = 0, len = features.length; j < len; j++) {
+
+ if ('casing-width' in features[j].style) {
+ queue.casings = queue.casings || [];
+ queue.casings.push(features[j]);
+ }
+ }
+ }
+
+ // lines
+ for (i = 0; i < layerIds.length; i++) {
+ features = layers[layerIds[i]];
+ queue = layersToRender[layerIds[i]] = layersToRender[layerIds[i]] || {};
+
+ for (j = 0, len = features.length; j < len; j++) {
+
+ if ('width' in features[j].style) {
+ queue.lines = queue.lines || [];
+ queue.lines.push(features[j]);
+ }
+ }
+ }
+
+ layerIds = ['_bg'].concat(layerIds);
+
+ for (i = 0; i < layerIds.length; i++) {
+ // begin modified by rurseekatze
+ //queue = layersToRender[layerIds[i]];
+ queue = layersToRender[layerIds[i]] = layersToRender[layerIds[i]] || {};
+ // end modified by rurseekatze
+ if (queue.polygons) {
+ for (j = 0, len = queue.polygons.length; j < len; j++) {
+ Kothic.polygon.render(ctx, queue.polygons[j], queue.polygons[j + 1], ws, hs, granularity);
+ }
+ }
+ if (queue.casings) {
+ ctx.lineCap = 'butt';
+ for (j = 0, len = queue.casings.length; j < len; j++) {
+ Kothic.line.renderCasing(ctx, queue.casings[j], queue.casings[j + 1], ws, hs, granularity);
+ }
+ }
+ if (queue.lines) {
+ ctx.lineCap = 'round';
+ for (j = 0, len = queue.lines.length; j < len; j++) {
+ Kothic.line.render(ctx, queue.lines[j], queue.lines[j + 1], ws, hs, granularity);
+ }
+ }
+ }
+ },
+
+ _renderTextAndIcons: function (layerIds, layers, ctx, ws, hs, collisionBuffer) {
+ //TODO: Move to the features detector
+ var j, style, i,
+ passes = [];
+
+ for (i = 0; i < layerIds.length; i++) {
+ var features = layers[layerIds[i]],
+ featuresLen = features.length;
+
+ // render icons without text
+ for (j = featuresLen - 1; j >= 0; j--) {
+ style = features[j].style;
+ if (style.hasOwnProperty('icon-image') && !style.text) {
+ Kothic.texticons.render(ctx, features[j], collisionBuffer, ws, hs, false, true);
+ }
+ }
+
+ // render text on features without icons
+ for (j = featuresLen - 1; j >= 0; j--) {
+ style = features[j].style;
+ if (!style.hasOwnProperty('icon-image') && style.text) {
+ Kothic.texticons.render(ctx, features[j], collisionBuffer, ws, hs, true, false);
+ }
+ }
+
+ // for features with both icon and text, render both or neither
+ for (j = featuresLen - 1; j >= 0; j--) {
+ style = features[j].style;
+ if (style.hasOwnProperty('icon-image') && style.text) {
+ Kothic.texticons.render(ctx, features[j], collisionBuffer, ws, hs, true, true);
+ }
+ }
+
+ // render shields with text
+ for (j = featuresLen - 1; j >= 0; j--) {
+ style = features[j].style;
+ if (style['shield-text']) {
+ Kothic.shields.render(ctx, features[j], collisionBuffer, ws, hs);
+ }
+ }
+ }
+
+ return passes;
+ }
+};
diff --git a/kothic/renderer/line.js b/kothic/renderer/line.js
new file mode 100644
index 0000000..296d349
--- /dev/null
+++ b/kothic/renderer/line.js
@@ -0,0 +1,121 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.line = {
+
+ renderCasing: function (ctx, feature, nextFeature, ws, hs, granularity) {
+ var style = feature.style,
+ nextStyle = nextFeature && nextFeature.style;
+
+ if (!this.pathOpened) {
+ this.pathOpened = true;
+ ctx.beginPath();
+ }
+
+ Kothic.path(ctx, feature, style["casing-dashes"] || style.dashes, false, ws, hs, granularity);
+
+ if (nextFeature &&
+ nextStyle.width === style.width &&
+ nextStyle['casing-width'] === style['casing-width'] &&
+ nextStyle['casing-color'] === style['casing-color'] &&
+ nextStyle['casing-dashes'] === style['casing-dashes'] &&
+ nextStyle['casing-opacity'] === style['casing-opacity']) {
+ return;
+ }
+
+ Kothic.style.setStyles(ctx, {
+ lineWidth: 2 * style["casing-width"] + (style.hasOwnProperty("width") ? style.width : 0),
+ strokeStyle: style["casing-color"] || "#000000",
+ lineCap: style["casing-linecap"] || style.linecap || "butt",
+ lineJoin: style["casing-linejoin"] || style.linejoin || "round",
+ globalAlpha: style["casing-opacity"] || 1
+ });
+
+ ctx.stroke();
+ this.pathOpened = false;
+ },
+
+ render: function (ctx, feature, nextFeature, ws, hs, granularity) {
+ var style = feature.style,
+ nextStyle = nextFeature && nextFeature.style;
+
+ if (!this.pathOpened) {
+ this.pathOpened = true;
+ ctx.beginPath();
+ }
+
+ Kothic.path(ctx, feature, style.dashes, false, ws, hs, granularity);
+
+ if (nextFeature &&
+ nextStyle.width === style.width &&
+ nextStyle.color === style.color &&
+ nextStyle.image === style.image &&
+ nextStyle.opacity === style.opacity) {
+ return;
+ }
+
+ if ('color' in style || !('image' in style)) {
+ var t_width = style.width || 1,
+ t_linejoin = "round",
+ t_linecap = "round";
+
+ if (t_width <= 2) {
+ t_linejoin = "miter";
+ t_linecap = "butt";
+ }
+ Kothic.style.setStyles(ctx, {
+ lineWidth: t_width,
+ strokeStyle: style.color || '#000000',
+ lineCap: style.linecap || t_linecap,
+ lineJoin: style.linejoin || t_linejoin,
+ globalAlpha: style.opacity || 1,
+ miterLimit: 4
+ });
+ ctx.stroke();
+ }
+
+
+ if ('image' in style) {
+ // second pass fills with texture
+ var image = MapCSS.getImage(style.image);
+
+ if (image) {
+ Kothic.style.setStyles(ctx, {
+ strokeStyle: ctx.createPattern(image, 'repeat') || "#000000",
+ lineWidth: style.width || 1,
+ lineCap: style.linecap || "round",
+ lineJoin: style.linejoin || "round",
+ globalAlpha: style.opacity || 1
+ });
+
+ ctx.stroke();
+ }
+ }
+ this.pathOpened = false;
+ },
+
+ pathOpened: false
+};
diff --git a/kothic/renderer/path.js b/kothic/renderer/path.js
new file mode 100644
index 0000000..d0364b4
--- /dev/null
+++ b/kothic/renderer/path.js
@@ -0,0 +1,167 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.path = (function () {
+ var dashPattern;
+
+ function setDashPattern(point, dashes) {
+ dashPattern = {
+ pattern: dashes,
+ seg: 0,
+ phs: 0,
+ x: point[0],
+ y: point[1]
+ };
+ }
+
+ function moveTo(ctx, point, dashes) {
+ ctx.moveTo(point[0], point[1]);
+ if (dashes) {
+ setDashPattern(point, dashes);
+ }
+ }
+
+ function dashTo(ctx, point) {
+ var pt = dashPattern,
+ dx = point[0] - pt.x,
+ dy = point[1] - pt.y,
+ dist = Math.sqrt(dx * dx + dy * dy),
+ x, more, t;
+
+ ctx.save();
+ ctx.translate(pt.x, pt.y);
+ ctx.rotate(Math.atan2(dy, dx));
+ ctx.moveTo(0, 0);
+
+ x = 0;
+ do {
+ t = pt.pattern[pt.seg];
+ x += t - pt.phs;
+ more = x < dist;
+
+ if (!more) {
+ pt.phs = t - (x - dist);
+ x = dist;
+ }
+
+ ctx[pt.seg % 2 ? 'moveTo' : 'lineTo'](x, 0);
+
+ if (more) {
+ pt.phs = 0;
+ pt.seg = ++pt.seg % pt.pattern.length;
+ }
+ } while (more);
+
+ pt.x = point[0];
+ pt.y = point[1];
+
+ ctx.restore();
+ }
+
+ function isTileBoundary(p, size) {
+ return p[0] === 0 || p[0] === size || p[1] === 0 || p[1] === size;
+ }
+
+ return function (ctx, feature, dashes, fill, ws, hs, granularity) {
+ var type = feature.type,
+ coords = feature.coordinates;
+
+ if (type === "Polygon") {
+ coords = [coords];
+ type = "MultiPolygon";
+ }
+
+ if (type === "LineString") {
+ coords = [coords];
+ type = "MultiLineString";
+ }
+
+ var i, j, k,
+ points,
+ len = coords.length,
+ len2, pointsLen,
+ prevPoint, point, screenPoint,
+ dx, dy, dist, pad = 50;
+
+ if (type === "MultiPolygon") {
+ for (i = 0; i < len; i++) {
+ for (k = 0, len2 = coords[i].length; k < len2; k++) {
+ points = coords[i][k];
+ pointsLen = points.length;
+ prevPoint = points[0];
+
+ for (j = 0; j <= pointsLen; j++) {
+ point = points[j] || points[0];
+ screenPoint = Kothic.geom.transformPoint(point, ws, hs);
+
+ if (j === 0 || (!fill &&
+ isTileBoundary(point, granularity) &&
+ isTileBoundary(prevPoint, granularity))) {
+ moveTo(ctx, screenPoint, dashes);
+ } else if (fill || !dashes) {
+ ctx.lineTo(screenPoint[0], screenPoint[1]);
+ } else {
+ dashTo(ctx, screenPoint);
+ }
+ prevPoint = point;
+ }
+ }
+ }
+ }
+
+ if (type === "MultiLineString") {
+ for (i = 0; i < len; i++) {
+ points = coords[i];
+ pointsLen = points.length;
+
+ for (j = 0; j < pointsLen; j++) {
+ point = points[j];
+ screenPoint = Kothic.geom.transformPoint(point, ws, hs);
+
+ // continue path off the tile by some abount to fix path edges between tiles
+ if ((j === 0 || j === pointsLen - 1) && isTileBoundary(point, granularity)) {
+ prevPoint = points[j ? pointsLen - 2 : 1];
+
+ dx = point[0] - prevPoint[0];
+ dy = point[1] - prevPoint[1];
+ dist = Math.sqrt(dx * dx + dy * dy);
+
+ screenPoint[0] = screenPoint[0] + pad * dx / dist;
+ screenPoint[1] = screenPoint[1] + pad * dy / dist;
+ }
+
+ if (j === 0) {
+ moveTo(ctx, screenPoint, dashes);
+ } else if (dashes) {
+ dashTo(ctx, screenPoint);
+ } else {
+ ctx.lineTo(screenPoint[0], screenPoint[1]);
+ }
+ }
+ }
+ }
+ };
+}());
diff --git a/kothic/renderer/polygon.js b/kothic/renderer/polygon.js
new file mode 100644
index 0000000..27085b9
--- /dev/null
+++ b/kothic/renderer/polygon.js
@@ -0,0 +1,83 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.polygon = {
+ render: function (ctx, feature, nextFeature, ws, hs, granularity) {
+ var style = feature.style,
+ nextStyle = nextFeature && nextFeature.style;
+
+ if (!this.pathOpened) {
+ this.pathOpened = true;
+ ctx.beginPath();
+ }
+
+ Kothic.path(ctx, feature, false, true, ws, hs, granularity);
+
+ if (nextFeature &&
+ (nextStyle['fill-color'] === style['fill-color']) &&
+ (nextStyle['fill-image'] === style['fill-image']) &&
+ (nextStyle['fill-opacity'] === style['fill-opacity'])) {
+ return;
+ }
+
+ this.fill(ctx, style);
+
+ this.pathOpened = false;
+ },
+
+ fill: function (ctx, style, fillFn) {
+ var opacity = style["fill-opacity"] || style.opacity, image;
+
+ if (style.hasOwnProperty('fill-color')) {
+ // first pass fills with solid color
+ Kothic.style.setStyles(ctx, {
+ fillStyle: style["fill-color"] || "#000000",
+ globalAlpha: opacity || 1
+ });
+ if (fillFn) {
+ fillFn();
+ } else {
+ ctx.fill();
+ }
+ }
+
+ if (style.hasOwnProperty('fill-image')) {
+ // second pass fills with texture
+ image = MapCSS.getImage(style['fill-image']);
+ if (image) {
+ Kothic.style.setStyles(ctx, {
+ fillStyle: ctx.createPattern(image, 'repeat'),
+ globalAlpha: opacity || 1
+ });
+ if (fillFn) {
+ fillFn();
+ } else {
+ ctx.fill();
+ }
+ }
+ }
+ }
+};
diff --git a/kothic/renderer/shields.js b/kothic/renderer/shields.js
new file mode 100644
index 0000000..f1c64ea
--- /dev/null
+++ b/kothic/renderer/shields.js
@@ -0,0 +1,145 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.shields = {
+ render: function (ctx, feature, collides, ws, hs) {
+ var style = feature.style, reprPoint = Kothic.geom.getReprPoint(feature),
+ point, img, len = 0, found = false, i, sgn;
+
+ if (!reprPoint) {
+ return;
+ }
+
+ point = Kothic.geom.transformPoint(reprPoint, ws, hs);
+
+ if (style["shield-image"]) {
+ img = MapCSS.getImage(style["icon-image"]);
+
+ if (!img) {
+ return;
+ }
+ }
+
+ Kothic.style.setStyles(ctx, {
+ font: Kothic.style.getFontString(style["shield-font-family"] || style["font-family"], style["shield-font-size"] || style["font-size"]),
+ fillStyle: style["shield-text-color"] || "#000000",
+ globalAlpha: style["shield-text-opacity"] || style.opacity || 1,
+ textAlign: 'center',
+ textBaseline: 'middle'
+ });
+
+ var text = String(style['shield-text']),
+ textWidth = ctx.measureText(text).width,
+ letterWidth = textWidth / text.length,
+ collisionWidth = textWidth + 2,
+ collisionHeight = letterWidth * 1.8;
+
+ if (feature.type === 'LineString') {
+ len = Kothic.geom.getPolyLength(feature.coordinates);
+
+ if (Math.max(collisionHeight / hs, collisionWidth / ws) > len) {
+ return;
+ }
+
+ for (i = 0, sgn = 1; i < len / 2; i += Math.max(len / 30, collisionHeight / ws), sgn *= -1) {
+ reprPoint = Kothic.geom.getAngleAndCoordsAtLength(feature.coordinates, len / 2 + sgn * i, 0);
+ if (!reprPoint) {
+ break;
+ }
+
+ reprPoint = [reprPoint[1], reprPoint[2]];
+
+ point = Kothic.geom.transformPoint(reprPoint, ws, hs);
+ if (img && (style["allow-overlap"] !== "true") &&
+ collides.checkPointWH(point, img.width, img.height, feature.kothicId)) {
+ continue;
+ }
+ if ((style["allow-overlap"] !== "true") &&
+ collides.checkPointWH(point, collisionWidth, collisionHeight, feature.kothicId)) {
+ continue;
+ }
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ return;
+ }
+
+ if (style["shield-casing-width"]) {
+ Kothic.style.setStyles(ctx, {
+ fillStyle: style["shield-casing-color"] || "#000000",
+ globalAlpha: style["shield-casing-opacity"] || style.opacity || 1
+ });
+ ctx.fillRect(point[0] - collisionWidth / 2 - (style["shield-casing-width"] || 0) - (style["shield-frame-width"] || 0),
+ point[1] - collisionHeight / 2 - (style["shield-casing-width"] || 0) - (style["shield-frame-width"] || 0),
+ collisionWidth + 2 * (style["shield-casing-width"] || 0) + 2 * (style["shield-frame-width"] || 0),
+ collisionHeight + 2 * (style["shield-casing-width"] || 0) + 2 * (style["shield-frame-width"] || 0));
+ }
+
+ if (style["shield-frame-width"]) {
+ Kothic.style.setStyles(ctx, {
+ fillStyle: style["shield-frame-color"] || "#000000",
+ globalAlpha: style["shield-frame-opacity"] || style.opacity || 1
+ });
+ ctx.fillRect(point[0] - collisionWidth / 2 - (style["shield-frame-width"] || 0),
+ point[1] - collisionHeight / 2 - (style["shield-frame-width"] || 0),
+ collisionWidth + 2 * (style["shield-frame-width"] || 0),
+ collisionHeight + 2 * (style["shield-frame-width"] || 0));
+ }
+
+ if (style["shield-color"]) {
+ Kothic.style.setStyles(ctx, {
+ fillStyle: style["shield-color"] || "#000000",
+ globalAlpha: style["shield-opacity"] || style.opacity || 1
+ });
+ ctx.fillRect(point[0] - collisionWidth / 2,
+ point[1] - collisionHeight / 2,
+ collisionWidth,
+ collisionHeight);
+ }
+
+ if (img) {
+ ctx.drawImage(img,
+ Math.floor(point[0] - img.width / 2),
+ Math.floor(point[1] - img.height / 2));
+ }
+ Kothic.style.setStyles(ctx, {
+ fillStyle: style["shield-text-color"] || "#000000",
+ globalAlpha: style["shield-text-opacity"] || style.opacity || 1
+ });
+
+ ctx.fillText(text, point[0], Math.ceil(point[1]));
+ if (img) {
+ collides.addPointWH(point, img.width, img.height, 0, feature.kothicId);
+ }
+
+ collides.addPointWH(point, collisionHeight, collisionWidth,
+ (parseFloat(style["shield-casing-width"]) || 0) + (parseFloat(style["shield-frame-width"]) || 0) + (parseFloat(style["-x-mapnik-min-distance"]) || 30), feature.kothicId);
+
+ }
+};
diff --git a/kothic/renderer/text.js b/kothic/renderer/text.js
new file mode 100644
index 0000000..4a09740
--- /dev/null
+++ b/kothic/renderer/text.js
@@ -0,0 +1,214 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.textOnPath = (function () {
+
+ function getWidth(ctx, text) {
+ return ctx.measureText(text).width;
+ }
+
+ function getTextCenter(axy, textWidth) {
+ return [axy[1] + 0.5 * Math.cos(axy[0]) * textWidth,
+ axy[2] + 0.5 * Math.sin(axy[0]) * textWidth];
+ }
+
+ function getCollisionParams(textWidth, axy, pxoffset) {
+ var textHeight = textWidth * 1.5,
+ cos = Math.abs(Math.cos(axy[0])),
+ sin = Math.abs(Math.sin(axy[0])),
+ w = cos * textWidth + sin * textHeight,
+ h = sin * textWidth + cos * textHeight;
+
+ return [getTextCenter(axy, textWidth + 2 * (pxoffset || 0)), w, h, 0];
+ }
+
+ function checkCollision(collisions, ctx, text, axy, letterWidth) {
+ var textWidth = getWidth(ctx, text);
+
+ for (var i = 0; i < textWidth; i += letterWidth) {
+ if (collisions.checkPointWH.apply(collisions, getCollisionParams(letterWidth, axy, i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function addCollision(collisions, ctx, text, axy, letterWidth) {
+ var textWidth = getWidth(ctx, text),
+ params = [];
+
+ for (var i = 0; i < textWidth; i += letterWidth) {
+ params.push(getCollisionParams(letterWidth, axy, i));
+ }
+ collisions.addPoints(params);
+ }
+
+ function renderText(ctx, axy, halo) {
+ var text = axy[4],
+ textCenter = getTextCenter(axy, getWidth(ctx, text));
+
+ ctx.translate(textCenter[0], textCenter[1]);
+ ctx.rotate(axy[0]);
+ ctx[halo ? 'strokeText' : 'fillText'](text, 0, 0);
+ ctx.rotate(-axy[0]);
+ ctx.translate(-textCenter[0], -textCenter[1]);
+ }
+
+ return function (ctx, points, text, halo, collisions) {
+ //widthCache = {};
+
+ // simplify points?
+
+ var textWidth = ctx.measureText(text).width,
+ textLen = text.length,
+ pathLen = Kothic.geom.getPolyLength(points);
+
+ if (pathLen < textWidth) {
+ return; // if label won't fit - don't try to
+ }
+
+ var avgLetterWidth = getWidth(ctx, 'a');
+
+ var letter,
+ widthUsed,
+ prevAngle,
+ positions,
+ solution = 0,
+ flipCount,
+ flipped = false,
+ axy,
+ letterWidth,
+ i,
+ maxAngle = Math.PI / 6;
+
+ // iterating solutions - start from center or from one of the ends
+ while (solution < 2) { //TODO change to for?
+ widthUsed = solution ? getWidth(ctx, text.charAt(0)) : (pathLen - textWidth) / 2; // ???
+ flipCount = 0;
+ prevAngle = null;
+ positions = [];
+
+ // iterating label letter by letter (should be fixed to support ligatures/CJK, ok for Cyrillic/latin)
+ for (i = 0; i < textLen; i++) {
+ letter = text.charAt(i);
+ letterWidth = getWidth(ctx, letter);
+ axy = Kothic.geom.getAngleAndCoordsAtLength(points, widthUsed, letterWidth);
+
+ // if cannot fit letter - restart with next solution
+ if (widthUsed >= pathLen || !axy) {
+ solution++;
+ positions = [];
+ if (flipped) { // if label was flipped, flip it back
+ points.reverse();
+ flipped = false;
+ }
+ break;
+ }
+
+ if (!prevAngle) {
+ prevAngle = axy[0];
+ }
+
+ // if label collisions with another, restart it from here
+ if (checkCollision(collisions, ctx, letter, axy, avgLetterWidth) || Math.abs(prevAngle - axy[0]) > maxAngle) {
+ widthUsed += letterWidth;
+ i = -1;
+ positions = [];
+ flipCount = 0;
+ continue;
+ }
+
+ while (letterWidth < axy[3] && i < textLen) { // try adding following letters to current, until line changes its direction
+ i++;
+ letter += text.charAt(i);
+ letterWidth = getWidth(ctx, letter);
+ if (checkCollision(collisions, ctx, letter, axy, avgLetterWidth)) {
+ i = 0;
+ widthUsed += letterWidth;
+ positions = [];
+ flipCount = 0;
+ letter = text.charAt(i);
+ letterWidth = getWidth(ctx, letter);
+ axy = Kothic.geom.getAngleAndCoordsAtLength(points, widthUsed, letterWidth);
+ break;
+ }
+ if (letterWidth >= axy[3]) {
+ i--;
+ letter = letter.slice(0, -1);
+ letterWidth = getWidth(ctx, letter);
+ break;
+ }
+ }
+
+ if (!axy) {
+ continue;
+ }
+
+ if ((axy[0] > (Math.PI / 2)) || (axy[0] < (-Math.PI / 2))) { // if current letters cluster was upside-down, count it
+ flipCount += letter.length;
+ }
+
+ prevAngle = axy[0];
+ axy.push(letter);
+ positions.push(axy);
+ widthUsed += letterWidth;
+ } //for
+
+ if (flipCount > textLen / 2) { // if more than half of the text is upside down, flip it and restart
+ points.reverse();
+ positions = [];
+
+ if (flipped) { // if it was flipped twice - restart with other start point solution
+ solution++;
+ points.reverse();
+ flipped = false;
+ } else {
+ flipped = true;
+ }
+ }
+
+ if (solution >= 2) {
+ return;
+ }
+
+ if (positions.length > 0) {
+ break;
+ }
+ } //while
+
+ var posLen = positions.length;
+
+ for (i = 0; halo && (i < posLen); i++) {
+ renderText(ctx, positions[i], true);
+ }
+
+ for (i = 0; i < posLen; i++) {
+ axy = positions[i];
+ renderText(ctx, axy);
+ addCollision(collisions, ctx, axy[4], axy, avgLetterWidth);
+ }
+ };
+}());
diff --git a/kothic/renderer/texticons.js b/kothic/renderer/texticons.js
new file mode 100644
index 0000000..0c2f2ee
--- /dev/null
+++ b/kothic/renderer/texticons.js
@@ -0,0 +1,118 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.texticons = {
+
+ render: function (ctx, feature, collides, ws, hs, renderText, renderIcon) {
+ var style = feature.style, img, point, w, h;
+
+ if (renderIcon || (renderText && feature.type !== 'LineString')) {
+ var reprPoint = Kothic.geom.getReprPoint(feature);
+ if (!reprPoint) {
+ return;
+ }
+ point = Kothic.geom.transformPoint(reprPoint, ws, hs);
+ }
+
+ if (renderIcon) {
+ img = MapCSS.getImage(style['icon-image']);
+ if (!img) { return; }
+
+ w = img.width;
+ h = img.height;
+
+ if (style['icon-width'] || style['icon-height']){
+ if (style['icon-width']) {
+ w = style['icon-width'];
+ h = img.height * w / img.width;
+ }
+ if (style['icon-height']) {
+ h = style['icon-height'];
+ if (!style['icon-width']) {
+ w = img.width * h / img.height;
+ }
+ }
+ }
+ if ((style['allow-overlap'] !== 'true') &&
+ collides.checkPointWH(point, w, h, feature.kothicId)) {
+ return;
+ }
+ }
+
+ if (renderText) {
+ Kothic.style.setStyles(ctx, {
+ lineWidth: style['text-halo-radius'] * 2,
+ font: Kothic.style.getFontString(style['font-family'], style['font-size'])
+ });
+
+ var text = String(style.text),
+ textWidth = ctx.measureText(text).width,
+ letterWidth = textWidth / text.length,
+ collisionWidth = textWidth,
+ collisionHeight = letterWidth * 2.5,
+ offset = style['text-offset'] || 0;
+
+ var halo = (style.hasOwnProperty('text-halo-radius'));
+
+ Kothic.style.setStyles(ctx, {
+ fillStyle: style['text-color'] || '#000000',
+ strokeStyle: style['text-halo-color'] || '#ffffff',
+ globalAlpha: style['text-opacity'] || style.opacity || 1,
+ textAlign: 'center',
+ textBaseline: 'middle'
+ });
+
+ if (feature.type === 'Polygon' || feature.type === 'Point') {
+ if ((style['text-allow-overlap'] !== 'true') &&
+ collides.checkPointWH([point[0], point[1] + offset], collisionWidth, collisionHeight, feature.kothicId)) {
+ return;
+ }
+
+ if (halo) {
+ ctx.strokeText(text, point[0], point[1] + offset);
+ }
+ ctx.fillText(text, point[0], point[1] + offset);
+
+ var padding = style['-x-kot-min-distance'] || 20;
+ collides.addPointWH([point[0], point[1] + offset], collisionWidth, collisionHeight, padding, feature.kothicId);
+
+ } else if (feature.type === 'LineString') {
+
+ var points = Kothic.geom.transformPoints(feature.coordinates, ws, hs);
+ Kothic.textOnPath(ctx, points, text, halo, collides);
+ }
+ }
+
+ if (renderIcon) {
+ ctx.drawImage(img,
+ Math.floor(point[0] - w / 2),
+ Math.floor(point[1] - h / 2), w, h);
+
+ var padding2 = parseFloat(style['-x-kot-min-distance']) || 0;
+ collides.addPointWH(point, w, h, padding2, feature.kothicId);
+ }
+ }
+};
diff --git a/kothic/style/mapcss.js b/kothic/style/mapcss.js
new file mode 100644
index 0000000..fe1674a
--- /dev/null
+++ b/kothic/style/mapcss.js
@@ -0,0 +1,339 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+var MapCSS = {
+ styles: {},
+ availableStyles: [],
+ images: {},
+ locales: [],
+ presence_tags: [],
+ value_tags: [],
+ cache: {},
+ debug: {hit: 0, miss: 0},
+
+ onError: function () {
+ },
+
+ onImagesLoad: function () {
+ },
+
+ /**
+ * Incalidate styles cache
+ */
+ invalidateCache: function () {
+ this.cache = {};
+ },
+
+ e_min: function (/*...*/) {
+ return Math.min.apply(null, arguments);
+ },
+
+ e_max: function (/*...*/) {
+ return Math.max.apply(null, arguments);
+ },
+
+ e_any: function (/*...*/) {
+ var i;
+
+ for (i = 0; i < arguments.length; i++) {
+ if (typeof(arguments[i]) !== 'undefined' && arguments[i] !== '') {
+ return arguments[i];
+ }
+ }
+
+ return '';
+ },
+
+ e_num: function (arg) {
+ if (!isNaN(parseFloat(arg))) {
+ return parseFloat(arg);
+ } else {
+ return '';
+ }
+ },
+
+ e_str: function (arg) {
+ return arg;
+ },
+
+ e_int: function (arg) {
+ return parseInt(arg, 10);
+ },
+
+ e_tag: function (obj, tag) {
+ if (obj.hasOwnProperty(tag) && obj[tag] !== null) {
+ return tag;
+ } else {
+ return '';
+ }
+ },
+
+ e_prop: function (obj, tag) {
+ if (obj.hasOwnProperty(tag) && obj[tag] !== null) {
+ return obj[tag];
+ } else {
+ return '';
+ }
+ },
+
+ e_sqrt: function (arg) {
+ return Math.sqrt(arg);
+ },
+
+ e_boolean: function (arg, if_exp, else_exp) {
+ if (typeof(if_exp) === 'undefined') {
+ if_exp = 'true';
+ }
+
+ if (typeof(else_exp) === 'undefined') {
+ else_exp = 'false';
+ }
+
+ if (arg === '0' || arg === 'false' || arg === '') {
+ return else_exp;
+ } else {
+ return if_exp;
+ }
+ },
+
+ e_metric: function (arg) {
+ if (/\d\s*mm$/.test(arg)) {
+ return 1000 * parseInt(arg, 10);
+ } else if (/\d\s*cm$/.test(arg)) {
+ return 100 * parseInt(arg, 10);
+ } else if (/\d\s*dm$/.test(arg)) {
+ return 10 * parseInt(arg, 10);
+ } else if (/\d\s*km$/.test(arg)) {
+ return 0.001 * parseInt(arg, 10);
+ } else if (/\d\s*in$/.test(arg)) {
+ return 0.0254 * parseInt(arg, 10);
+ } else if (/\d\s*ft$/.test(arg)) {
+ return 0.3048 * parseInt(arg, 10);
+ } else {
+ return parseInt(arg, 10);
+ }
+ },
+
+ e_zmetric: function (arg) {
+ return MapCSS.e_metric(arg);
+ },
+
+ // begin modified by rurseekatze: extended localizing so that captions made of more than one tag can be used
+ e_localize: function (tags, text) {
+ var locales = MapCSS.locales, i, j, tag;
+ var tagcombination = text;
+ var keys = tagcombination.split(" ");
+
+ // replace keys by localized keys if existing
+ for (j = 0; j < keys.length; j++) {
+ for (i = 0; i < locales.length; i++) {
+ tag = keys[j] + ':' + locales[i];
+ if (tags[tag]) {
+ tagcombination = tagcombination.replace(tag, tags[tag]);
+ }
+ }
+ }
+
+ // replace keys by values
+ for (j = 0; j < keys.length; j++) {
+ if (tags[keys[j]]) {
+ tagcombination = tagcombination.replace(keys[j], tags[keys[j]]);
+ }
+ else {
+ tagcombination = tagcombination.replace(keys[j], "");
+ }
+ }
+
+ return tagcombination.trim();
+ },
+ // end modified by rurseekatze
+
+ // begin added by rurseekatze: added support for concat method in MapCSS styles
+ e_concat: function () {
+ var tagString = "";
+
+ for (var i = 0; i < arguments.length; i++)
+ tagString = tagString.concat(arguments[i]);
+
+ return tagString;
+ },
+ // end added by rurseekatze
+
+ loadStyle: function (style, restyle, sprite_images, external_images, presence_tags, value_tags) {
+ var i;
+ sprite_images = sprite_images || [];
+ external_images = external_images || [];
+
+ if (presence_tags) {
+ for (i = 0; i < presence_tags.length; i++) {
+ if (this.presence_tags.indexOf(presence_tags[i]) < 0) {
+ this.presence_tags.push(presence_tags[i]);
+ }
+ }
+ }
+
+ if (value_tags) {
+ for (i = 0; i < value_tags.length; i++) {
+ if (this.value_tags.indexOf(value_tags[i]) < 0) {
+ this.value_tags.push(value_tags[i]);
+ }
+ }
+ }
+
+ MapCSS.styles[style] = {
+ restyle: restyle,
+ images: sprite_images,
+ external_images: external_images,
+ textures: {},
+ sprite_loaded: !sprite_images,
+ external_images_loaded: !external_images.length
+ };
+
+ MapCSS.availableStyles.push(style);
+ },
+
+ /**
+ * Call MapCSS.onImagesLoad callback if all sprite and external
+ * images was loaded
+ */
+ _onImagesLoad: function (style) {
+ if (MapCSS.styles[style].external_images_loaded &&
+ MapCSS.styles[style].sprite_loaded) {
+ MapCSS.onImagesLoad();
+ }
+ },
+
+ preloadSpriteImage: function (style, url) {
+ var images = MapCSS.styles[style].images,
+ img = new Image();
+
+ delete MapCSS.styles[style].images;
+
+ img.onload = function () {
+ var image;
+ for (image in images) {
+ if (images.hasOwnProperty(image)) {
+ images[image].sprite = img;
+ MapCSS.images[image] = images[image];
+ }
+ }
+ MapCSS.styles[style].sprite_loaded = true;
+ MapCSS._onImagesLoad(style);
+ };
+ img.onerror = function (e) {
+ MapCSS.onError(e);
+ };
+ img.src = url;
+ },
+
+ preloadExternalImages: function (style, urlPrefix) {
+ var external_images = MapCSS.styles[style].external_images;
+ delete MapCSS.styles[style].external_images;
+
+ urlPrefix = urlPrefix || '';
+ var len = external_images.length, loaded = 0, i;
+
+ function loadImage(url) {
+ var img = new Image();
+ img.onload = function () {
+ loaded++;
+ MapCSS.images[url] = {
+ sprite: img,
+ height: img.height,
+ width: img.width,
+ offset: 0
+ };
+ if (loaded === len) {
+ MapCSS.styles[style].external_images_loaded = true;
+ MapCSS._onImagesLoad(style);
+ }
+ };
+ img.onerror = function () {
+ loaded++;
+ if (loaded === len) {
+ MapCSS.styles[style].external_images_loaded = true;
+ MapCSS._onImagesLoad(style);
+ }
+ };
+ img.src = url;
+ }
+
+ for (i = 0; i < len; i++) {
+ loadImage(urlPrefix + external_images[i]);
+ }
+ },
+
+ getImage: function (ref) {
+ var img = MapCSS.images[ref];
+
+ if (img && img.sprite) {
+ var canvas = document.createElement('canvas');
+ canvas.width = img.width;
+ canvas.height = img.height;
+
+ canvas.getContext('2d').drawImage(img.sprite,
+ 0, img.offset, img.width, img.height,
+ 0, 0, img.width, img.height);
+
+ img = MapCSS.images[ref] = canvas;
+ }
+
+ return img;
+ },
+
+ getTagKeys: function (tags, zoom, type, selector) {
+ var keys = [], i;
+ for (i = 0; i < this.presence_tags.length; i++) {
+ if (tags.hasOwnProperty(this.presence_tags[i])) {
+ keys.push(this.presence_tags[i]);
+ }
+ }
+
+ for (i = 0; i < this.value_tags.length; i++) {
+ if (tags.hasOwnProperty(this.value_tags[i])) {
+ keys.push(this.value_tags[i] + ':' + tags[this.value_tags[i]]);
+ }
+ }
+
+ return [zoom, type, selector, keys.join(':')].join(':');
+ },
+
+ restyle: function (styleNames, tags, zoom, type, selector) {
+ var i, key = this.getTagKeys(tags, zoom, type, selector), actions = this.cache[key] || {};
+
+ if (!this.cache.hasOwnProperty(key)) {
+ this.debug.miss += 1;
+ for (i = 0; i < styleNames.length; i++) {
+ actions = MapCSS.styles[styleNames[i]].restyle(actions, tags, zoom, type, selector);
+ }
+ this.cache[key] = actions;
+ } else {
+ this.debug.hit += 1;
+ }
+
+ return actions;
+ }
+};
diff --git a/kothic/style/style.js b/kothic/style/style.js
new file mode 100644
index 0000000..82e150d
--- /dev/null
+++ b/kothic/style/style.js
@@ -0,0 +1,153 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.style = {
+
+ defaultCanvasStyles: {
+ strokeStyle: 'rgba(0,0,0,0.5)',
+ fillStyle: 'rgba(0,0,0,0.5)',
+ lineWidth: 1,
+ lineCap: 'round',
+ lineJoin: 'round',
+ textAlign: 'center',
+ textBaseline: 'middle'
+ },
+
+ populateLayers: function (features, zoom, styles) {
+ var layers = {},
+ i, len, feature, layerId, layerStyle;
+
+ var styledFeatures = Kothic.style.styleFeatures(features, zoom, styles);
+
+ for (i = 0, len = styledFeatures.length; i < len; i++) {
+ feature = styledFeatures[i];
+ layerStyle = feature.style['-x-mapnik-layer'];
+ layerId = !layerStyle ? feature.properties.layer || 0 :
+ layerStyle === 'top' ? 10000 : -10000;
+
+ // added by rurseekatze: possibility to override layering
+ layerId = (feature.style['kothicjs-ignore-layer'] == 'true') ? 0 : layerId;
+ // end added by rurseekatze
+
+ layers[layerId] = layers[layerId] || [];
+ layers[layerId].push(feature);
+ }
+
+ return layers;
+ },
+
+ getStyle: function (feature, zoom, styleNames) {
+ var shape = feature.type,
+ type, selector;
+ if (shape === 'LineString' || shape === 'MultiLineString') {
+ type = 'way';
+ selector = 'line';
+ } else if (shape === 'Polygon' || shape === 'MultiPolygon') {
+ type = 'way';
+ selector = 'area';
+ } else if (shape === 'Point' || shape === 'MultiPoint') {
+ type = 'node';
+ selector = 'node';
+ }
+
+ return MapCSS.restyle(styleNames, feature.properties, zoom, type, selector);
+ },
+
+ styleFeatures: function (features, zoom, styleNames) {
+ var styledFeatures = [],
+ i, j, len, feature, style, restyledFeature, k;
+
+ for (i = 0, len = features.length; i < len; i++) {
+ feature = features[i];
+ style = this.getStyle(feature, zoom, styleNames);
+
+ for (j in style) {
+ if (j === 'default') {
+ restyledFeature = feature;
+ } else {
+ restyledFeature = {};
+ for (k in feature) {
+ restyledFeature[k] = feature[k];
+ }
+ }
+
+ restyledFeature.kothicId = i + 1;
+ restyledFeature.style = style[j];
+ restyledFeature.zIndex = style[j]['z-index'] || 0;
+ restyledFeature.sortKey = (style[j]['fill-color'] || '') + (style[j].color || '');
+ styledFeatures.push(restyledFeature);
+ }
+ }
+
+ styledFeatures.sort(function (a, b) {
+ return a.zIndex !== b.zIndex ? a.zIndex - b.zIndex :
+ a.sortKey < b.sortKey ? -1 :
+ a.sortKey > b.sortKey ? 1 : 0;
+ });
+
+ return styledFeatures;
+ },
+
+ getFontString: function (name, size) {
+ name = name || '';
+ size = size || 9;
+
+ var family = name ? name + ', ' : '';
+
+ name = name.toLowerCase();
+
+ var styles = [];
+ if (name.indexOf('italic') !== -1 || name.indexOf('oblique') !== -1) {
+ styles.push('italic');
+ }
+ if (name.indexOf('bold') !== -1) {
+ styles.push('bold');
+ //family += '''+name.replace('bold', '')+'', ';
+ family += name.replace('bold', '') + ', ';
+ }
+
+ styles.push(size + 'px');
+
+ if (name.indexOf('serif') !== -1) {
+ family += 'Georgia, serif';
+ } else {
+ family += '"Helvetica Neue", Arial, Helvetica, sans-serif';
+ }
+ styles.push(family);
+
+
+ return styles.join(' ');
+ },
+
+ setStyles: function (ctx, styles) {
+ var i;
+ for (i in styles) {
+ if (styles.hasOwnProperty(i)) {
+ ctx[i] = styles[i];
+ }
+ }
+ }
+};
diff --git a/kothic/utils/collisions.js b/kothic/utils/collisions.js
new file mode 100644
index 0000000..6079237
--- /dev/null
+++ b/kothic/utils/collisions.js
@@ -0,0 +1,78 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.CollisionBuffer = function (height, width) {
+ this.buffer = rbush();
+ this.height = height;
+ this.width = width;
+};
+
+Kothic.CollisionBuffer.prototype = {
+ addPointWH: function (point, w, h, d, id) {
+ this.buffer.insert(this.getBoxFromPoint(point, w, h, d, id));
+ },
+
+ addPoints: function (params) {
+ var points = [];
+ for (var i = 0, len = params.length; i < len; i++) {
+ points.push(this.getBoxFromPoint.apply(this, params[i]));
+ }
+ this.buffer.load(points);
+ },
+
+ checkBox: function (b, id) {
+ var result = this.buffer.search(b),
+ i, len;
+
+ if (b[0] < 0 || b[1] < 0 || b[2] > this.width || b[3] > this.height) { return true; }
+
+ for (i = 0, len = result.length; i < len; i++) {
+ // if it's the same object (only different styles), don't detect collision
+ if (id !== result[i][4]) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ checkPointWH: function (point, w, h, id) {
+ return this.checkBox(this.getBoxFromPoint(point, w, h, 0), id);
+ },
+
+ getBoxFromPoint: function (point, w, h, d, id) {
+ var dx = w / 2 + d,
+ dy = h / 2 + d;
+
+ return [
+ point[0] - dx,
+ point[1] - dy,
+ point[0] + dx,
+ point[1] + dy,
+ id
+ ];
+ }
+};
diff --git a/kothic/utils/geom.js b/kothic/utils/geom.js
new file mode 100644
index 0000000..f684e2e
--- /dev/null
+++ b/kothic/utils/geom.js
@@ -0,0 +1,134 @@
+/*
+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of
+ conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
+ of conditions and the following disclaimer in the documentation and/or other materials
+ provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+Kothic.geom = {
+ transformPoint: function (point, ws, hs) {
+ return [ws * point[0], hs * point[1]];
+ },
+
+ transformPoints: function (points, ws, hs) {
+ var transformed = [], i, len;
+ for (i = 0, len = points.length; i < len; i++) {
+ transformed.push(this.transformPoint(points[i], ws, hs));
+ }
+ return transformed;
+ },
+
+ getReprPoint: function (feature) {
+ var point, len;
+ switch (feature.type) {
+ case 'Point':
+ point = feature.coordinates;
+ break;
+ case 'Polygon':
+ point = feature.reprpoint;
+ break;
+ case 'LineString':
+ len = Kothic.geom.getPolyLength(feature.coordinates);
+ point = Kothic.geom.getAngleAndCoordsAtLength(feature.coordinates, len / 2, 0);
+ point = [point[1], point[2]];
+ break;
+ case 'GeometryCollection':
+ //TODO: Disassemble geometry collection
+ return;
+ case 'MultiPoint':
+ //TODO: Disassemble multi point
+ return;
+ case 'MultiPolygon':
+ point = feature.reprpoint;
+ break;
+ case 'MultiLineString':
+ //TODO: Disassemble geometry collection
+ return;
+ }
+ return point;
+ },
+
+ getPolyLength: function (points) {
+ var pointsLen = points.length,
+ c, pc, i,
+ dx, dy,
+ len = 0;
+
+ for (i = 1; i < pointsLen; i++) {
+ c = points[i];
+ pc = points[i - 1];
+ dx = pc[0] - c[0];
+ dy = pc[1] - c[1];
+ len += Math.sqrt(dx * dx + dy * dy);
+ }
+ return len;
+ },
+
+ getAngleAndCoordsAtLength: function (points, dist, width) {
+ var pointsLen = points.length,
+ dx, dy, x, y,
+ i, c, pc,
+ len = 0,
+ segLen = 0,
+ angle, partLen, sameseg = true,
+ gotxy = false;
+
+ width = width || 0; // by default we think that a letter is 0 px wide
+
+ for (i = 1; i < pointsLen; i++) {
+ if (gotxy) {
+ sameseg = false;
+ }
+
+ c = points[i];
+ pc = points[i - 1];
+
+ dx = c[0] - pc[0];
+ dy = c[1] - pc[1];
+ segLen = Math.sqrt(dx * dx + dy * dy);
+
+ if (!gotxy && len + segLen >= dist) {
+ partLen = dist - len;
+ x = pc[0] + dx * partLen / segLen;
+ y = pc[1] + dy * partLen / segLen;
+
+ gotxy = true;
+ }
+
+ if (gotxy && len + segLen >= dist + width) {
+ partLen = dist + width - len;
+ dx = pc[0] + dx * partLen / segLen;
+ dy = pc[1] + dy * partLen / segLen;
+ angle = Math.atan2(dy - y, dx - x);
+
+ if (sameseg) {
+ return [angle, x, y, segLen - partLen];
+ } else {
+ return [angle, x, y, 0];
+ }
+ }
+
+ len += segLen;
+ }
+ }
+};
+
diff --git a/kothic/utils/rbush.js b/kothic/utils/rbush.js
new file mode 100644
index 0000000..cf3df37
--- /dev/null
+++ b/kothic/utils/rbush.js
@@ -0,0 +1,556 @@
+/*
+ (c) 2013, Vladimir Agafonkin
+ RBush, a JavaScript library for high-performance 2D spatial indexing of points and rectangles.
+ https://github.com/mourner/rbush
+
+ Copyright (c) 2013 Vladimir Agafonkin
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+(function () { 'use strict';
+
+function rbush(maxEntries, format) {
+
+ // jshint newcap: false, validthis: true
+ if (!(this instanceof rbush)) { return new rbush(maxEntries, format); }
+
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
+ this._maxEntries = Math.max(4, maxEntries || 9);
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
+
+ if (format) {
+ this._initFormat(format);
+ }
+
+ this.clear();
+}
+
+rbush.prototype = {
+
+ all: function () {
+ return this._all(this.data, []);
+ },
+
+ search: function (bbox) {
+
+ var node = this.data,
+ result = [];
+
+ if (!this._intersects(bbox, node.bbox)) { return result; }
+
+ var nodesToSearch = [],
+ i, len, child, childBBox;
+
+ while (node) {
+ for (i = 0, len = node.children.length; i < len; i++) {
+ child = node.children[i];
+ childBBox = node.leaf ? this.toBBox(child) : child.bbox;
+
+ if (this._intersects(bbox, childBBox)) {
+
+ if (node.leaf) {
+ result.push(child);
+
+ } else if (this._contains(bbox, childBBox)) {
+ this._all(child, result);
+
+ } else {
+ nodesToSearch.push(child);
+ }
+ }
+ }
+
+ node = nodesToSearch.pop();
+ }
+
+ return result;
+ },
+
+ load: function (data) {
+ if (!(data && data.length)) { return this; }
+
+ if (data.length < this._minEntries) {
+ for (var i = 0, len = data.length; i < len; i++) {
+ this.insert(data[i]);
+ }
+ return this;
+ }
+
+ // recursively build the tree with the given data from stratch using OMT algorithm
+ var node = this._build(data.slice(), 0);
+
+ if (!this.data.children.length) {
+ // save as is if tree is empty
+ this.data = node;
+
+ } else if (this.data.height === node.height) {
+ // split root if trees have the same height
+ this._splitRoot(this.data, node);
+
+ } else {
+ if (this.data.height < node.height) {
+ // swap trees if inserted one is bigger
+ var tmpNode = this.data;
+ this.data = node;
+ node = tmpNode;
+ }
+
+ // insert the small tree into the large tree at appropriate level
+ this._insert(node, this.data.height - node.height - 1, true);
+ }
+
+ return this;
+ },
+
+ insert: function (item) {
+ if (item) {
+ this._insert(item, this.data.height - 1);
+ }
+ return this;
+ },
+
+ clear: function () {
+ this.data = {
+ children: [],
+ leaf: true,
+ bbox: this._empty(),
+ height: 1
+ };
+ return this;
+ },
+
+ remove: function (item) {
+ if (!item) { return this; }
+
+ var node = this.data,
+ bbox = this.toBBox(item),
+ path = [],
+ indexes = [],
+ i, parent, index, goingUp;
+
+ // depth-first iterative tree traversal
+ while (node || path.length) {
+
+ if (!node) { // go up
+ node = path.pop();
+ parent = path[path.length - 1];
+ i = indexes.pop();
+ goingUp = true;
+ }
+
+ if (node.leaf) { // check current node
+ index = node.children.indexOf(item);
+
+ if (index !== -1) {
+ // item found, remove the item and condense tree upwards
+ node.children.splice(index, 1);
+ path.push(node);
+ this._condense(path);
+ return this;
+ }
+ }
+
+ if (!goingUp && !node.leaf && this._contains(node.bbox, bbox)) { // go down
+ path.push(node);
+ indexes.push(i);
+ i = 0;
+ parent = node;
+ node = node.children[0];
+
+ } else if (parent) { // go right
+ i++;
+ node = parent.children[i];
+ goingUp = false;
+
+ } else { // nothing found
+ node = null;
+ }
+ }
+
+ return this;
+ },
+
+ toBBox: function (item) { return item; },
+
+ compareMinX: function (a, b) { return a[0] - b[0]; },
+ compareMinY: function (a, b) { return a[1] - b[1]; },
+
+ toJSON: function () { return this.data; },
+
+ fromJSON: function (data) {
+ this.data = data;
+ return this;
+ },
+
+ _all: function (node, result) {
+ var nodesToSearch = [];
+ while (node) {
+ if (node.leaf) {
+ result.push.apply(result, node.children);
+ } else {
+ nodesToSearch.push.apply(nodesToSearch, node.children);
+ }
+ node = nodesToSearch.pop();
+ }
+ return result;
+ },
+
+ _build: function (items, level, height) {
+
+ var N = items.length,
+ M = this._maxEntries,
+ node;
+
+ if (N <= M) {
+ node = {
+ children: items,
+ leaf: true,
+ height: 1
+ };
+ this._calcBBox(node);
+ return node;
+ }
+
+ if (!level) {
+ // target height of the bulk-loaded tree
+ height = Math.ceil(Math.log(N) / Math.log(M));
+
+ // target number of root entries to maximize storage utilization
+ M = Math.ceil(N / Math.pow(M, height - 1));
+
+ items.sort(this.compareMinX);
+ }
+
+ // TODO eliminate recursion?
+
+ node = {
+ children: [],
+ height: height
+ };
+
+ var N1 = Math.ceil(N / M) * Math.ceil(Math.sqrt(M)),
+ N2 = Math.ceil(N / M),
+ compare = level % 2 === 1 ? this.compareMinX : this.compareMinY,
+ i, j, slice, sliceLen, childNode;
+
+ // split the items into M mostly square tiles
+ for (i = 0; i < N; i += N1) {
+ slice = items.slice(i, i + N1).sort(compare);
+
+ for (j = 0, sliceLen = slice.length; j < sliceLen; j += N2) {
+ // pack each entry recursively
+ childNode = this._build(slice.slice(j, j + N2), level + 1, height - 1);
+ node.children.push(childNode);
+ }
+ }
+
+ this._calcBBox(node);
+
+ return node;
+ },
+
+ _chooseSubtree: function (bbox, node, level, path) {
+
+ var i, len, child, targetNode, area, enlargement, minArea, minEnlargement;
+
+ while (true) {
+ path.push(node);
+
+ if (node.leaf || path.length - 1 === level) { break; }
+
+ minArea = minEnlargement = Infinity;
+
+ for (i = 0, len = node.children.length; i < len; i++) {
+ child = node.children[i];
+ area = this._area(child.bbox);
+ enlargement = this._enlargedArea(bbox, child.bbox) - area;
+
+ // choose entry with the least area enlargement
+ if (enlargement < minEnlargement) {
+ minEnlargement = enlargement;
+ minArea = area < minArea ? area : minArea;
+ targetNode = child;
+
+ } else if (enlargement === minEnlargement) {
+ // otherwise choose one with the smallest area
+ if (area < minArea) {
+ minArea = area;
+ targetNode = child;
+ }
+ }
+ }
+
+ node = targetNode;
+ }
+
+ return node;
+ },
+
+ _insert: function (item, level, isNode) {
+
+ var bbox = isNode ? item.bbox : this.toBBox(item),
+ insertPath = [];
+
+ // find the best node for accommodating the item, saving all nodes along the path too
+ var node = this._chooseSubtree(bbox, this.data, level, insertPath);
+
+ // put the item into the node
+ node.children.push(item);
+ this._extend(node.bbox, bbox);
+
+ // split on node overflow; propagate upwards if necessary
+ while (level >= 0) {
+ if (insertPath[level].children.length > this._maxEntries) {
+ this._split(insertPath, level);
+ level--;
+ } else {
+ break;
+ }
+ }
+
+ // adjust bboxes along the insertion path
+ this._adjustParentBBoxes(bbox, insertPath, level);
+ },
+
+ // split overflowed node into two
+ _split: function (insertPath, level) {
+
+ var node = insertPath[level],
+ M = node.children.length,
+ m = this._minEntries;
+
+ this._chooseSplitAxis(node, m, M);
+
+ var newNode = {
+ children: node.children.splice(this._chooseSplitIndex(node, m, M)),
+ height: node.height
+ };
+
+ if (node.leaf) {
+ newNode.leaf = true;
+ }
+
+ this._calcBBox(node);
+ this._calcBBox(newNode);
+
+ if (level) {
+ insertPath[level - 1].children.push(newNode);
+ } else {
+ this._splitRoot(node, newNode);
+ }
+ },
+
+ _splitRoot: function (node, newNode) {
+ // split root node
+ this.data = {};
+ this.data.children = [node, newNode];
+ this.data.height = node.height + 1;
+ this._calcBBox(this.data);
+ },
+
+ _chooseSplitIndex: function (node, m, M) {
+
+ var i, bbox1, bbox2, overlap, area, minOverlap, minArea, index;
+
+ minOverlap = minArea = Infinity;
+
+ for (i = m; i <= M - m; i++) {
+ bbox1 = this._distBBox(node, 0, i);
+ bbox2 = this._distBBox(node, i, M);
+
+ overlap = this._intersectionArea(bbox1, bbox2);
+ area = this._area(bbox1) + this._area(bbox2);
+
+ // choose distribution with minimum overlap
+ if (overlap < minOverlap) {
+ minOverlap = overlap;
+ index = i;
+
+ minArea = area < minArea ? area : minArea;
+
+ } else if (overlap === minOverlap) {
+ // otherwise choose distribution with minimum area
+ if (area < minArea) {
+ minArea = area;
+ index = i;
+ }
+ }
+ }
+
+ return index;
+ },
+
+ // sorts node children by the best axis for split
+ _chooseSplitAxis: function (node, m, M) {
+
+ var compareMinX = node.leaf ? this.compareMinX : this._compareNodeMinX,
+ compareMinY = node.leaf ? this.compareMinY : this._compareNodeMinY,
+ xMargin = this._allDistMargin(node, m, M, compareMinX),
+ yMargin = this._allDistMargin(node, m, M, compareMinY);
+
+ // if total distributions margin value is minimal for x, sort by minX,
+ // otherwise it's already sorted by minY
+
+ if (xMargin < yMargin) {
+ node.children.sort(compareMinX);
+ }
+ },
+
+ // total margin of all possible split distributions where each node is at least m full
+ _allDistMargin: function (node, m, M, compare) {
+
+ node.children.sort(compare);
+
+ var leftBBox = this._distBBox(node, 0, m),
+ rightBBox = this._distBBox(node, M - m, M),
+ margin = this._margin(leftBBox) + this._margin(rightBBox),
+ i, child;
+
+ for (i = m; i < M - m; i++) {
+ child = node.children[i];
+ this._extend(leftBBox, node.leaf ? this.toBBox(child) : child.bbox);
+ margin += this._margin(leftBBox);
+ }
+
+ for (i = M - m - 1; i >= m; i--) {
+ child = node.children[i];
+ this._extend(rightBBox, node.leaf ? this.toBBox(child) : child.bbox);
+ margin += this._margin(rightBBox);
+ }
+
+ return margin;
+ },
+
+ // min bounding rectangle of node children from k to p-1
+ _distBBox: function (node, k, p) {
+ var bbox = this._empty();
+
+ for (var i = k, child; i < p; i++) {
+ child = node.children[i];
+ this._extend(bbox, node.leaf ? this.toBBox(child) : child.bbox);
+ }
+
+ return bbox;
+ },
+
+ // calculate node's bbox from bboxes of its children
+ _calcBBox: function (node) {
+ node.bbox = this._distBBox(node, 0, node.children.length);
+ },
+
+ _adjustParentBBoxes: function (bbox, path, level) {
+ // adjust bboxes along the given tree path
+ for (var i = level; i >= 0; i--) {
+ this._extend(path[i].bbox, bbox);
+ }
+ },
+
+ _condense: function (path) {
+ // go through the path, removing empty nodes and updating bboxes
+ for (var i = path.length - 1, siblings; i >= 0; i--) {
+ if (path[i].children.length === 0) {
+ if (i > 0) {
+ siblings = path[i - 1].children;
+ siblings.splice(siblings.indexOf(path[i]), 1);
+ } else {
+ this.clear();
+ }
+ } else {
+ this._calcBBox(path[i]);
+ }
+ }
+ },
+
+ _contains: function(a, b) {
+ return a[0] <= b[0] &&
+ a[1] <= b[1] &&
+ b[2] <= a[2] &&
+ b[3] <= a[3];
+ },
+
+ _intersects: function (a, b) {
+ return b[0] <= a[2] &&
+ b[1] <= a[3] &&
+ b[2] >= a[0] &&
+ b[3] >= a[1];
+ },
+
+ _extend: function (a, b) {
+ a[0] = Math.min(a[0], b[0]);
+ a[1] = Math.min(a[1], b[1]);
+ a[2] = Math.max(a[2], b[2]);
+ a[3] = Math.max(a[3], b[3]);
+ return a;
+ },
+
+ _area: function (a) { return (a[2] - a[0]) * (a[3] - a[1]); },
+ _margin: function (a) { return (a[2] - a[0]) + (a[3] - a[1]); },
+
+ _enlargedArea: function (a, b) {
+ return (Math.max(b[2], a[2]) - Math.min(b[0], a[0])) *
+ (Math.max(b[3], a[3]) - Math.min(b[1], a[1]));
+ },
+
+ _intersectionArea: function (a, b) {
+ var minX = Math.max(a[0], b[0]),
+ minY = Math.max(a[1], b[1]),
+ maxX = Math.min(a[2], b[2]),
+ maxY = Math.min(a[3], b[3]);
+
+ return Math.max(0, maxX - minX) *
+ Math.max(0, maxY - minY);
+ },
+
+ _empty: function () { return [Infinity, Infinity, -Infinity, -Infinity]; },
+
+ _compareNodeMinX: function (a, b) { return a.bbox[0] - b.bbox[0]; },
+ _compareNodeMinY: function (a, b) { return a.bbox[1] - b.bbox[1]; },
+
+ _initFormat: function (format) {
+ // data format (minX, minY, maxX, maxY accessors)
+
+ // uses eval-type function compilation instead of just accepting a toBBox function
+ // because the algorithms are very sensitive to sorting functions performance,
+ // so they should be dead simple and without inner calls
+
+ // jshint evil: true
+
+ var compareArr = ['return a', ' - b', ';'];
+
+ this.compareMinX = new Function('a', 'b', compareArr.join(format[0]));
+ this.compareMinY = new Function('a', 'b', compareArr.join(format[1]));
+
+ this.toBBox = new Function('a', 'return [a' + format.join(', a') + '];');
+ }
+};
+
+if (typeof define === 'function' && define.amd) {
+ define(function() {
+ return rbush;
+ });
+} else if (typeof module !== 'undefined') {
+ module.exports = rbush;
+} else if (typeof self !== 'undefined') {
+ self.rbush = rbush;
+} else {
+ window.rbush = rbush;
+}
+
+})();
diff --git a/patches/kothic-leaflet.diff b/patches/kothic-leaflet.diff
deleted file mode 100644
index 40004b7..0000000
--- a/patches/kothic-leaflet.diff
+++ /dev/null
@@ -1,103 +0,0 @@
---- /home/alexander/Desktop/kothic-leaflet.js
-+++ /home/alexander/Projekte/OpenRailwayMap/orm-latest/js/kothic-leaflet.js
-@@ -1,9 +1,35 @@
-+/*
-+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
-+All rights reserved.
-+
-+Redistribution and use in source and binary forms, with or without modification, are
-+permitted provided that the following conditions are met:
-+
-+ 1. Redistributions of source code must retain the above copyright notice, this list of
-+ conditions and the following disclaimer.
-+
-+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
-+ of conditions and the following disclaimer in the documentation and/or other materials
-+ provided with the distribution.
-+
-+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-+*/
-+
-+
- L.TileLayer.Kothic = L.TileLayer.Canvas.extend({
- options: {
-- tileSize: 256 * 4,
-- zoomOffset: 2,
-+ tileSize: 256,
-+ zoomOffset: 0,
- minZoom: 2,
-- maxZoom: 22,
-+ maxZoom: 19,
- updateWhenIdle: true,
- unloadInvisibleTiles: true,
- attribution: 'Map data © 2013 OpenStreetMap contributors,' +
-@@ -41,13 +67,13 @@
- delete layer._scripts[key];
- }
-
-- this._invertYAxe(data);
-+ // modified by rurseekatze
-+ // this._invertYAxe(data);
-
- var styles = this.options.styles;
-
- Kothic.render(canvas, data, zoom + zoomOffset, {
- styles: styles,
-- locales: ['be', 'ru', 'en'],
- onRenderComplete: onRenderComplete
- });
-
-@@ -69,9 +95,10 @@
- },
-
- enableStyle: function(name) {
-- if (MapCSS.availableStyles.indexOf(name) >= 0 && this.options.styles.indexOf(name) < 0) {
-+ // start modified by rurseekatze
-+ if (this.options.styles.indexOf(name) == -1) {
-+ // end modified by rurseekatze
- this.options.styles.push(name);
-- this.redraw();
- }
- },
-
-@@ -79,19 +106,26 @@
- if (this.options.styles.indexOf(name) >= 0) {
- var i = this.options.styles.indexOf(name);
- this.options.styles.splice(i, 1);
-- this.redraw();
- }
- },
-
-+ // start modified by rurseekatze
- redraw: function() {
- MapCSS.invalidateCache();
-- // TODO implement layer.redraw() in Leaflet
-+
- this._map.getPanes().tilePane.empty = false;
-- if (this._map && this._map._container) {
-- this._reset();
-- this._update();
-- }
-+
-+ if (this._map) {
-+ this._reset({hard: true});
-+ this._update();
-+ }
-+
-+ for (var i in this._tiles) {
-+ this._redrawTile(this._tiles[i]);
-+ }
-+ return this;
- },
-+ // end modified by rurseekatze
-
- _invertYAxe: function(data) {
- var type, coordinates, tileSize = data.granularity, i, j, k, l, feature;
diff --git a/patches/kothic.diff b/patches/kothic.diff
deleted file mode 100644
index 4a9407e..0000000
--- a/patches/kothic.diff
+++ /dev/null
@@ -1,85 +0,0 @@
---- /home/alexander/Desktop/kothic.js
-+++ /home/alexander/Projekte/OpenRailwayMap/orm-latest/js/kothic.js
-@@ -1,8 +1,28 @@
- /*
-- (c) 2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
-- Kothic JS is a full-featured JavaScript map rendering engine using HTML5 Canvas.
-- http://github.com/kothic/kothic-js
-+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
-+All rights reserved.
-+
-+Redistribution and use in source and binary forms, with or without modification, are
-+permitted provided that the following conditions are met:
-+
-+ 1. Redistributions of source code must retain the above copyright notice, this list of
-+ conditions and the following disclaimer.
-+
-+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
-+ of conditions and the following disclaimer in the documentation and/or other materials
-+ provided with the distribution.
-+
-+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-+
-
- var Kothic = {
-
-@@ -35,7 +55,7 @@
- ws = width / granularity, hs = height / granularity,
- collisionBuffer = new Kothic.CollisionBuffer(height, width);
-
-- console.time('styles');
-+ //console.time('styles');
-
- // setup layer styles
- var layers = Kothic.style.populateLayers(data.features, zoom, styles),
-@@ -44,10 +64,10 @@
- // render the map
- Kothic.style.setStyles(ctx, Kothic.style.defaultCanvasStyles);
-
-- console.timeEnd('styles');
-+ //console.timeEnd('styles');
-
- Kothic.getFrame(function () {
-- console.time('geometry');
-+ //console.time('geometry');
-
- Kothic._renderBackground(ctx, width, height, zoom, styles);
- Kothic._renderGeometryFeatures(layerIds, layers, ctx, ws, hs, granularity);
-@@ -56,12 +76,12 @@
- options.onRenderComplete();
- }
-
-- console.timeEnd('geometry');
-+ //console.timeEnd('geometry');
-
- Kothic.getFrame(function () {
-- console.time('text/icons');
-+ //console.time('text/icons');
- Kothic._renderTextAndIcons(layerIds, layers, ctx, ws, hs, collisionBuffer);
-- console.timeEnd('text/icons');
-+ //console.timeEnd('text/icons');
-
- //Kothic._renderCollisions(ctx, collisionBuffer.buffer.data);
- });
-@@ -166,8 +186,10 @@
- layerIds = ['_bg'].concat(layerIds);
-
- for (i = 0; i < layerIds.length; i++) {
-- queue = layersToRender[layerIds[i]];
--
-+ // begin modified by rurseekatze
-+ //queue = layersToRender[layerIds[i]];
-+ queue = layersToRender[layerIds[i]] = layersToRender[layerIds[i]] || {};
-+ // end modified by rurseekatze
- if (queue.polygons) {
- for (j = 0, len = queue.polygons.length; j < len; j++) {
- Kothic.polygon.render(ctx, queue.polygons[j], queue.polygons[j + 1], ws, hs, granularity);
diff --git a/patches/mapcss.diff b/patches/mapcss.diff
deleted file mode 100644
index 61f8e61..0000000
--- a/patches/mapcss.diff
+++ /dev/null
@@ -1,89 +0,0 @@
---- /home/alexander/Desktop/mapcss.js
-+++ /home/alexander/Projekte/OpenRailwayMap/orm-latest/js/style/mapcss.js
-@@ -1,3 +1,28 @@
-+/*
-+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
-+All rights reserved.
-+
-+Redistribution and use in source and binary forms, with or without modification, are
-+permitted provided that the following conditions are met:
-+
-+ 1. Redistributions of source code must retain the above copyright notice, this list of
-+ conditions and the following disclaimer.
-+
-+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
-+ of conditions and the following disclaimer in the documentation and/or other materials
-+ provided with the distribution.
-+
-+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-+*/
-+
-
- var MapCSS = {
- styles: {},
-@@ -116,18 +141,46 @@
- return MapCSS.e_metric(arg);
- },
-
-+ // begin modified by rurseekatze: extended localizing so that captions made of more than one tag can be used
- e_localize: function (tags, text) {
-- var locales = MapCSS.locales, i, tag;
--
-- for (i = 0; i < locales.length; i++) {
-- tag = text + ':' + locales[i];
-- if (tags[tag]) {
-- return tags[tag];
-- }
-- }
--
-- return tags[text];
-- },
-+ var locales = MapCSS.locales, i, j, tag;
-+ var tagcombination = text;
-+ var keys = tagcombination.split(" ");
-+
-+ // replace keys by localized keys if existing
-+ for (j = 0; j < keys.length; j++) {
-+ for (i = 0; i < locales.length; i++) {
-+ tag = keys[j] + ':' + locales[i];
-+ if (tags[tag]) {
-+ tagcombination = tagcombination.replace(tag, tags[tag]);
-+ }
-+ }
-+ }
-+
-+ // replace keys by values
-+ for (j = 0; j < keys.length; j++) {
-+ if (tags[keys[j]]) {
-+ tagcombination = tagcombination.replace(keys[j], tags[keys[j]]);
-+ }
-+ else {
-+ tagcombination = tagcombination.replace(keys[j], "");
-+ }
-+ }
-+
-+ return tagcombination.trim();
-+ },
-+ // end modified by rurseekatze
-+
-+ // begin added by rurseekatze: added support for concat method in MapCSS styles
-+ e_concat: function () {
-+ var tagString = "";
-+
-+ for (var i = 0; i < arguments.length; i++)
-+ tagString = tagString.concat(arguments[i]);
-+
-+ return tagString;
-+ },
-+ // end added by rurseekatze
-
- loadStyle: function (style, restyle, sprite_images, external_images, presence_tags, value_tags) {
- var i;
diff --git a/patches/style.diff b/patches/style.diff
deleted file mode 100644
index 109e728..0000000
--- a/patches/style.diff
+++ /dev/null
@@ -1,42 +0,0 @@
---- /home/alexander/Desktop/style.js
-+++ /home/alexander/Projekte/OpenRailwayMap/orm-latest/js/style/style.js
-@@ -1,3 +1,28 @@
-+/*
-+Copyright (c) 2011-2013, Darafei Praliaskouski, Vladimir Agafonkin, Maksim Gurtovenko
-+All rights reserved.
-+
-+Redistribution and use in source and binary forms, with or without modification, are
-+permitted provided that the following conditions are met:
-+
-+ 1. Redistributions of source code must retain the above copyright notice, this list of
-+ conditions and the following disclaimer.
-+
-+ 2. Redistributions in binary form must reproduce the above copyright notice, this list
-+ of conditions and the following disclaimer in the documentation and/or other materials
-+ provided with the distribution.
-+
-+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
-+COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
-+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
-+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-+*/
-+
-
- Kothic.style = {
-
-@@ -22,6 +47,10 @@
- layerStyle = feature.style['-x-mapnik-layer'];
- layerId = !layerStyle ? feature.properties.layer || 0 :
- layerStyle === 'top' ? 10000 : -10000;
-+
-+ // added by rurseekatze: possibility to override layering
-+ layerId = (feature.style['kothicjs-ignore-layer'] == 'true') ? 0 : layerId;
-+ // end added by rurseekatze
-
- layers[layerId] = layers[layerId] || [];
- layers[layerId].push(feature);