├── .gitignore
├── .npmignore
├── docs
├── img
│ ├── intro.png
│ ├── miter.png
│ ├── round.png
│ ├── overdraw.png
│ ├── WebGL_Logo.png
│ └── stadia-logo.svg
├── overdraw.html
├── basic-demo.html
├── js
│ ├── leaflet-hash.js
│ ├── L.PixiOverlay.min.js
│ └── graph-draw-bundle.min.js
├── data
│ └── polyline.json
├── city-mesh.html
├── railways.html
├── polyline.html
└── css
│ └── leaflet.css
├── LICENSE
├── package.json
├── src
├── tessellator.js
└── graph-draw.js
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | private
3 | dist
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | docs
2 | docs_src
3 | private
4 |
--------------------------------------------------------------------------------
/docs/img/intro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/intro.png
--------------------------------------------------------------------------------
/docs/img/miter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/miter.png
--------------------------------------------------------------------------------
/docs/img/round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/round.png
--------------------------------------------------------------------------------
/docs/img/overdraw.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/overdraw.png
--------------------------------------------------------------------------------
/docs/img/WebGL_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manubb/graph-draw/HEAD/docs/img/WebGL_Logo.png
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Manuel Baclet
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/overdraw.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Expected overdraw situation
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "graph-draw",
3 | "description": "tessellation of planar graphs with convex polygons",
4 | "author": "Manuel Baclet ",
5 | "version": "2.0.4",
6 | "main": "src/graph-draw.js",
7 | "scripts": {
8 | "build:docs": "cp ./node_modules/leaflet-hash/leaflet-hash.js ./docs/js/leaflet-hash.js && cp ./node_modules/leaflet/dist/leaflet.css ./docs/css/leaflet.css && cp ./node_modules/leaflet/dist/leaflet.js ./docs/js/leaflet.js && cp ./node_modules/leaflet-pixi-overlay/L.PixiOverlay.min.js ./docs/js/L.PixiOverlay.min.js && cp ./node_modules/pixi.js/dist/pixi.min.js ./docs/js/pixi.min.js && cp ./dist/graph-draw-bundle.min.js ./docs/js/graph-draw-bundle.min.js",
9 | "build:js": "./node_modules/browserify/bin/cmd.js ./src/graph-draw.js --standalone graphDraw > ./dist/graph-draw-bundle.js && ./node_modules/browserify/bin/cmd.js --external libtess ./src/graph-draw.js --standalone graphDraw > ./dist/graph-draw.js",
10 | "build:uglify": "./node_modules/uglify-js/bin/uglifyjs ./dist/graph-draw-bundle.js -m -c -o ./dist/graph-draw-bundle.min.js && ./node_modules/uglify-js/bin/uglifyjs ./dist/graph-draw.js -m -c -o ./dist/graph-draw.min.js",
11 | "build": "npm run build:js && npm run build:uglify"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/manubb/graph-draw.git"
16 | },
17 | "keywords": [
18 | "planar graph",
19 | "tessellation",
20 | "graph",
21 | "polyline"
22 | ],
23 | "license": "MIT",
24 | "homepage": "https://github.com/manubb/graph-draw#readme",
25 | "devDependencies": {
26 | "browserify": "^17.0.1",
27 | "leaflet": "^1.7.1",
28 | "leaflet-hash": "^0.2.1",
29 | "leaflet-pixi-overlay": "^1.5.0",
30 | "pixi.js": "^4.7.3",
31 | "uglify-js": "^3.19.3"
32 | },
33 | "bugs": {
34 | "url": "https://github.com/manubb/graph-draw/issues"
35 | },
36 | "dependencies": {
37 | "libtess": "^1.2.2"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/docs/basic-demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Basic graph-draw demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/src/tessellator.js:
--------------------------------------------------------------------------------
1 | // Graph-draw Tesselator
2 | // author: Manuel Baclet
3 | // license: MIT
4 |
5 | 'use strict';
6 |
7 | var tess;
8 |
9 | try {
10 | tess = require('libtess');
11 | } catch (e) {
12 | if (typeof window.libtess === 'undefined') {
13 | throw new Error('libtess must be loaded before graph-draw');
14 | }
15 | tess = window.libtess;
16 | }
17 |
18 | var GluTesselator = tess.GluTesselator, gluEnum = tess.gluEnum, primitiveType = tess.primitiveType;
19 |
20 | var GLU_TESS_BOUNDARY_ONLY = gluEnum.GLU_TESS_BOUNDARY_ONLY;
21 | var GL_LINE_LOOP = tess.primitiveType.GL_LINE_LOOP;
22 |
23 | function Tesselator() {
24 | GluTesselator.call(this);
25 |
26 | this.gluTessNormal(0, 0, 1);
27 | this.gluTessProperty(gluEnum.GLU_TESS_WINDING_RULE, tess.windingRule.GLU_TESS_WINDING_POSITIVE);
28 |
29 | this.gluTessCallback(gluEnum.GLU_TESS_VERTEX_DATA, this._vertex);
30 | this.gluTessCallback(gluEnum.GLU_TESS_BEGIN, this._begin);
31 | this.gluTessCallback(gluEnum.GLU_TESS_END, this._end);
32 | this.gluTessCallback(gluEnum.GLU_TESS_ERROR, this._error);
33 | this.gluTessCallback(gluEnum.GLU_TESS_COMBINE, this._combine);
34 | }
35 |
36 | Tesselator.prototype = Object.create(GluTesselator.prototype);
37 | Tesselator.prototype.constructor = Tesselator;
38 |
39 | Tesselator.prototype.run = function(polygons) {
40 | var self = this;
41 | this.gluTessBeginPolygon();
42 | polygons.forEach(function(poly) {
43 | self.gluTessBeginContour();
44 | poly.forEach(function(point) {
45 | self.gluTessVertex([point[0], point[1], 0], point);
46 | });
47 | self.gluTessEndContour();
48 | });
49 | this.gluTessProperty(GLU_TESS_BOUNDARY_ONLY, true);
50 | this._output = [];
51 | this.gluTessEndPolygon();
52 |
53 | this.gluTessBeginPolygon();
54 | this._output.forEach(function(poly) {
55 | self.gluTessBeginContour();
56 | poly.forEach(function(point) {
57 | self.gluTessVertex([point[0], point[1], 0], point);
58 | });
59 | self.gluTessEndContour();
60 | });
61 | this.gluTessProperty(GLU_TESS_BOUNDARY_ONLY, false);
62 | this.gluTessEndPolygon();
63 | };
64 |
65 | Tesselator.prototype.clean = function() {
66 | this._accu = [];
67 | this._output = [];
68 | };
69 |
70 | Tesselator.prototype._begin = function(type) {
71 | this._primitiveType = type;
72 | this._accu = [];
73 | };
74 |
75 | Tesselator.prototype._vertex = function(v) {
76 | this._accu.push(v);
77 | };
78 |
79 | Tesselator.prototype._error = function(err) {
80 | console.error('libtess error: ' + err);
81 | };
82 |
83 | Tesselator.prototype._combine = function(v) {
84 | return [v[0], v[1]];
85 | };
86 |
87 | Tesselator.prototype._end = function() {
88 | if (this._primitiveType === GL_LINE_LOOP) {
89 | this._output.push(this._accu);
90 | } else {
91 | for (var i = 0; i < this._accu.length; i += 3) {
92 | this._cb([this._accu[i], this._accu[i+1], this._accu[i+2]]);
93 | }
94 | }
95 | };
96 |
97 | module.exports = Tesselator;
98 |
--------------------------------------------------------------------------------
/docs/img/stadia-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
9 |
10 |
11 |
15 |
16 |
17 |
20 |
21 |
22 |
24 |
25 |
26 |
29 |
30 |
31 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | graph-draw
2 | ==========
3 |
4 | A JavaScript library for tessellating undirected planar graphs for Node and browsers.
5 |
6 | [](https://manubb.github.io/graph-draw/basic-demo.html)
7 |
8 | The algorithm is designed to avoid local overdraw. A typical non local overdraw (expected) situation:
9 | [](https://manubb.github.io/graph-draw/overdraw.html)
10 |
11 | The library can be used for example to draw boundaries or polylines on a [Leaflet](http://leafletjs.com) map using [leaflet-pixi-overlay](https://www.npmjs.com/package/leaflet-pixi-overlay) (see the demos).
12 | ## Demo
13 |
14 | A very basic [demo](https://manubb.github.io/graph-draw/basic-demo.html).
15 |
16 | A [polyline](https://manubb.github.io/graph-draw/polyline.html) on a map (173 edges tessellated in 335 triangles).
17 |
18 | French [cities boundaries](https://manubb.github.io/graph-draw/city-mesh.html) (186722 edges tessellated in 769041 triangles).
19 |
20 | A [rail network](https://manubb.github.io/graph-draw/railways.html).
21 |
22 | ## Installation
23 | graph-draw is available as a npm package:
24 | ```
25 | npm install graph-draw
26 | ```
27 | In Node:
28 | ```js
29 | const graphDraw = require('graph-draw');
30 | ```
31 |
32 | In browsers, include one file from the dist directory. (Files with name that contains "bundle" include libtess.)
33 |
34 | ## Usage
35 | ```js
36 | const vertices = [[0, 0], [100, 0], [100, 100], [0, 100]];
37 | // coordinates of the vertices
38 |
39 | const edges = [[0, 1], [1, 2], [2, 3], [3, 0], [1, 3]];
40 | // each edge is specified with the indices of the linked vertices
41 | // each edge must appear exactly once in the list
42 | // ([0, 1] and [1, 0] are the same edge)
43 |
44 | const graph = {vertices, edges};
45 | const strokeWidth = 10;
46 | const polygons = [];
47 |
48 | const polygonCallBack = convexPolygon => polygons.push(convexPolygon);
49 |
50 | graphDraw(graph, strokeWidth, polygonCallBack);
51 | ```
52 | The `polygonCallBack` is executed on each polygon of the tessellation. Now, `polygons` contains a list of convex polygons (which can be easily converted into triangle strips or triangle fans):
53 |
54 | ```js
55 | [
56 | [[x1, y1], [x2, y2], [x3, y3], [x4, y4]],
57 | [[a1, b1], [a2, b2], [a3, b3]],
58 | ...
59 | ]
60 | ```
61 |
62 | Those convex polygons can have between 3 and 8 edges.
63 |
64 | ## Limiting miters
65 | When the angle between two consecutive edges is close to 2π, long miter situations occur. For example:
66 |
67 | ```js
68 | const vertices = [
69 | [0, -200],
70 | [100 , -100],
71 | [30, -200]
72 | ];
73 |
74 | const edges = [
75 | [0, 1],
76 | [1, 2]
77 | ];
78 | const graph = {vertices, edges};
79 | const strokeWidth = 20;
80 | graphDraw(graph, strokeWidth, polygonCallBack);
81 | ```
82 | produces:
83 | 
84 |
85 | To avoid this, `graphDraw` function accepts a fourth (optional) `maxAngle` parameter which is an angle between π and 2π. If the angle between two consecutive edges is above `maxAngle`, the miter will be replaced by two triangles approximating a round join. For example:
86 |
87 | ```js
88 | graphDraw(graph, strokeWidth, polygonCallBack, Math.PI);
89 | ```
90 | will produce:
91 | 
92 |
93 | ## Sponsors
94 |
95 | Thanks to [Stadia Maps](https://stadiamaps.com/) for providing the [Stamen Toner Lite map tiles](https://docs.stadiamaps.com/map-styles/stamen-toner/#lite-variant) in our project demos.
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/docs/js/leaflet-hash.js:
--------------------------------------------------------------------------------
1 | (function(window) {
2 | var HAS_HASHCHANGE = (function() {
3 | var doc_mode = window.documentMode;
4 | return ('onhashchange' in window) &&
5 | (doc_mode === undefined || doc_mode > 7);
6 | })();
7 |
8 | L.Hash = function(map) {
9 | this.onHashChange = L.Util.bind(this.onHashChange, this);
10 |
11 | if (map) {
12 | this.init(map);
13 | }
14 | };
15 |
16 | L.Hash.parseHash = function(hash) {
17 | if(hash.indexOf('#') === 0) {
18 | hash = hash.substr(1);
19 | }
20 | var args = hash.split("/");
21 | if (args.length == 3) {
22 | var zoom = parseInt(args[0], 10),
23 | lat = parseFloat(args[1]),
24 | lon = parseFloat(args[2]);
25 | if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) {
26 | return false;
27 | } else {
28 | return {
29 | center: new L.LatLng(lat, lon),
30 | zoom: zoom
31 | };
32 | }
33 | } else {
34 | return false;
35 | }
36 | };
37 |
38 | L.Hash.formatHash = function(map) {
39 | var center = map.getCenter(),
40 | zoom = map.getZoom(),
41 | precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
42 |
43 | return "#" + [zoom,
44 | center.lat.toFixed(precision),
45 | center.lng.toFixed(precision)
46 | ].join("/");
47 | },
48 |
49 | L.Hash.prototype = {
50 | map: null,
51 | lastHash: null,
52 |
53 | parseHash: L.Hash.parseHash,
54 | formatHash: L.Hash.formatHash,
55 |
56 | init: function(map) {
57 | this.map = map;
58 |
59 | // reset the hash
60 | this.lastHash = null;
61 | this.onHashChange();
62 |
63 | if (!this.isListening) {
64 | this.startListening();
65 | }
66 | },
67 |
68 | removeFrom: function(map) {
69 | if (this.changeTimeout) {
70 | clearTimeout(this.changeTimeout);
71 | }
72 |
73 | if (this.isListening) {
74 | this.stopListening();
75 | }
76 |
77 | this.map = null;
78 | },
79 |
80 | onMapMove: function() {
81 | // bail if we're moving the map (updating from a hash),
82 | // or if the map is not yet loaded
83 |
84 | if (this.movingMap || !this.map._loaded) {
85 | return false;
86 | }
87 |
88 | var hash = this.formatHash(this.map);
89 | if (this.lastHash != hash) {
90 | location.replace(hash);
91 | this.lastHash = hash;
92 | }
93 | },
94 |
95 | movingMap: false,
96 | update: function() {
97 | var hash = location.hash;
98 | if (hash === this.lastHash) {
99 | return;
100 | }
101 | var parsed = this.parseHash(hash);
102 | if (parsed) {
103 | this.movingMap = true;
104 |
105 | this.map.setView(parsed.center, parsed.zoom);
106 |
107 | this.movingMap = false;
108 | } else {
109 | this.onMapMove(this.map);
110 | }
111 | },
112 |
113 | // defer hash change updates every 100ms
114 | changeDefer: 100,
115 | changeTimeout: null,
116 | onHashChange: function() {
117 | // throttle calls to update() so that they only happen every
118 | // `changeDefer` ms
119 | if (!this.changeTimeout) {
120 | var that = this;
121 | this.changeTimeout = setTimeout(function() {
122 | that.update();
123 | that.changeTimeout = null;
124 | }, this.changeDefer);
125 | }
126 | },
127 |
128 | isListening: false,
129 | hashChangeInterval: null,
130 | startListening: function() {
131 | this.map.on("moveend", this.onMapMove, this);
132 |
133 | if (HAS_HASHCHANGE) {
134 | L.DomEvent.addListener(window, "hashchange", this.onHashChange);
135 | } else {
136 | clearInterval(this.hashChangeInterval);
137 | this.hashChangeInterval = setInterval(this.onHashChange, 50);
138 | }
139 | this.isListening = true;
140 | },
141 |
142 | stopListening: function() {
143 | this.map.off("moveend", this.onMapMove, this);
144 |
145 | if (HAS_HASHCHANGE) {
146 | L.DomEvent.removeListener(window, "hashchange", this.onHashChange);
147 | } else {
148 | clearInterval(this.hashChangeInterval);
149 | }
150 | this.isListening = false;
151 | }
152 | };
153 | L.hash = function(map) {
154 | return new L.Hash(map);
155 | };
156 | L.Map.prototype.addHash = function() {
157 | this._hash = L.hash(this);
158 | };
159 | L.Map.prototype.removeHash = function() {
160 | this._hash.removeFrom();
161 | };
162 | })(window);
163 |
--------------------------------------------------------------------------------
/docs/data/polyline.json:
--------------------------------------------------------------------------------
1 | {"vertices":[[-77.066104,38.910203],[-77.066106,38.910321],[-77.066112,38.910758],[-77.065249,38.910775],[-77.065178,38.910793],[-77.065139,38.910804],[-77.064904,38.91036],[-77.064855,38.910268],[-77.064621,38.909825],[-77.06459,38.909766],[-77.064342,38.909298],[-77.064281,38.909182],[-77.064201,38.909226],[-77.064175,38.909235],[-77.064149,38.909241],[-77.064122,38.909244],[-77.06336,38.90926],[-77.061442,38.909288],[-77.060801,38.909297],[-77.059237,38.909315],[-77.058186,38.90933],[-77.057113,38.909343],[-77.055623,38.909368],[-77.054762,38.909377],[-77.053951,38.909389],[-77.053904,38.909393],[-77.053858,38.909399],[-77.053813,38.909408],[-77.052833,38.909635],[-77.052799,38.909642],[-77.052772,38.909645],[-77.052692,38.90965],[-77.052443,38.909649],[-77.05096,38.909639],[-77.050327,38.909634],[-77.049545,38.909631],[-77.049533,38.909635],[-77.049341,38.909635],[-77.048903,38.909635],[-77.048797,38.909634],[-77.04773,38.909639],[-77.046632,38.90964],[-77.045758,38.909641],[-77.044877,38.909643],[-77.044578,38.909641],[-77.044493,38.909635],[-77.04442,38.909626],[-77.044345,38.909608],[-77.044283,38.909578],[-77.044249,38.909541],[-77.044212,38.909496],[-77.044199,38.909458],[-77.044182,38.909421],[-77.044162,38.909385],[-77.04414,38.90935],[-77.044113,38.909313],[-77.044083,38.90928],[-77.044051,38.909247],[-77.044018,38.909165],[-77.044018,38.909117],[-77.044029,38.909071],[-77.044037,38.909038],[-77.044058,38.908981],[-77.043996,38.909021],[-77.043919,38.909052],[-77.043854,38.909067],[-77.043798,38.909073],[-77.043755,38.909075],[-77.043689,38.909056],[-77.043656,38.909048],[-77.04359,38.909036],[-77.043527,38.909028],[-77.043486,38.909026],[-77.043444,38.909025],[-77.043402,38.909026],[-77.043344,38.909029],[-77.043286,38.909037],[-77.043263,38.909041],[-77.04323,38.909047],[-77.043194,38.909056],[-77.043022,38.909118],[-77.042995,38.909132],[-77.04296,38.909151],[-77.042938,38.909165],[-77.042915,38.90918],[-77.042875,38.909209],[-77.04284,38.909239],[-77.042823,38.909255],[-77.042791,38.909288],[-77.042774,38.909307],[-77.042712,38.909315],[-77.042637,38.909322],[-77.042551,38.909317],[-77.042422,38.909299],[-77.041676,38.90903],[-77.039935,38.908425],[-77.038503,38.907925],[-77.037656,38.907632],[-77.037477,38.907534],[-77.037317,38.907438],[-77.037238,38.90739],[-77.037121,38.907315],[-77.036997,38.907233],[-77.036875,38.907137],[-77.036833,38.907094],[-77.036817,38.907081],[-77.036795,38.907068],[-77.036774,38.907058],[-77.036767,38.907052],[-77.036712,38.907034],[-77.03669,38.907028],[-77.036643,38.907022],[-77.03661,38.907021],[-77.03653,38.907021],[-77.036474,38.907021],[-77.03643,38.907025],[-77.03639,38.907033],[-77.036295,38.907058],[-77.036005,38.906995],[-77.035439,38.906869],[-77.035142,38.906757],[-77.035049,38.906725],[-77.0348,38.906641],[-77.034673,38.906594],[-77.034568,38.906558],[-77.034059,38.906382],[-77.033931,38.906338],[-77.032623,38.905883],[-77.03129,38.905426],[-77.030031,38.904982],[-77.029623,38.904835],[-77.028082,38.904304],[-77.027679,38.904167],[-77.02705,38.903949],[-77.026409,38.903729],[-77.025984,38.903579],[-77.024321,38.902997],[-77.024139,38.902954],[-77.023952,38.902954],[-77.023188,38.902956],[-77.022699,38.902958],[-77.021917,38.90296],[-77.021918,38.902902],[-77.021918,38.902521],[-77.019907,38.902521],[-77.018928,38.902521],[-77.016176,38.902519],[-77.015177,38.902518],[-77.013722,38.902514],[-77.012171,38.902516],[-77.011237,38.902516],[-77.009846,38.902515],[-77.009117,38.902514],[-77.009126,38.901346],[-77.00912,38.900203],[-77.009062,38.900203],[-77.008975,38.900203],[-77.008004,38.900198],[-77.00631,38.900193],[-77.005531,38.900197],[-77.002929,38.9002],[-77.002038,38.900203],[-77.001892,38.900203],[-77.000571,38.900205],[-76.999507,38.900204],[-76.998442,38.900204],[-76.998369,38.900204],[-76.996167,38.900205],[-76.994961,38.900203],[-76.994962,38.899748],[-76.994961,38.899626],[-76.994961,38.899367],[-76.994961,38.898908],[-76.994962,38.898447]],"edges":[[1,0],[2,1],[3,2],[4,3],[5,4],[6,5],[7,6],[8,7],[9,8],[10,9],[11,10],[12,11],[13,12],[14,13],[15,14],[16,15],[17,16],[18,17],[19,18],[20,19],[21,20],[22,21],[23,22],[24,23],[25,24],[26,25],[27,26],[28,27],[29,28],[30,29],[31,30],[32,31],[33,32],[34,33],[35,34],[36,35],[37,36],[38,37],[39,38],[40,39],[41,40],[42,41],[43,42],[44,43],[45,44],[46,45],[47,46],[48,47],[49,48],[50,49],[51,50],[52,51],[53,52],[54,53],[55,54],[56,55],[57,56],[58,57],[59,58],[60,59],[61,60],[62,61],[63,62],[64,63],[65,64],[66,65],[67,66],[68,67],[69,68],[70,69],[71,70],[72,71],[73,72],[74,73],[75,74],[76,75],[77,76],[78,77],[79,78],[80,79],[81,80],[82,81],[83,82],[84,83],[85,84],[86,85],[87,86],[88,87],[89,88],[90,89],[91,90],[92,91],[93,92],[94,93],[95,94],[96,95],[97,96],[98,97],[99,98],[100,99],[101,100],[102,101],[103,102],[104,103],[105,104],[106,105],[107,106],[108,107],[109,108],[110,109],[111,110],[112,111],[113,112],[114,113],[115,114],[116,115],[117,116],[118,117],[119,118],[120,119],[121,120],[122,121],[123,122],[124,123],[125,124],[126,125],[127,126],[128,127],[129,128],[130,129],[131,130],[132,131],[133,132],[134,133],[135,134],[136,135],[137,136],[138,137],[139,138],[140,139],[141,140],[142,141],[143,142],[144,143],[145,144],[146,145],[147,146],[148,147],[149,148],[150,149],[151,150],[152,151],[153,152],[154,153],[155,154],[156,155],[157,156],[158,157],[159,158],[160,159],[161,160],[162,161],[163,162],[164,163],[165,164],[166,165],[167,166],[168,167],[169,168],[170,169],[171,170],[172,171],[173,172]]}
2 |
--------------------------------------------------------------------------------
/docs/js/L.PixiOverlay.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("function"==typeof define&&define.amd)define(["leaflet","pixi.js"],e);else if("undefined"!=typeof module)module.exports=e(require("leaflet"),require("pixi.js"));else{if(void 0===window.L)throw new Error("Leaflet must be loaded first");if(void 0===window.PIXI)throw new Error("Pixi.js must be loaded first");e(window.L,window.PIXI)}}(function(d,o){function e(){return this}var t=d.Point.prototype._round;function r(e,t,i){e=o.VERSION<"7"?e.plugins.interaction:e.events;t?e.destroy():i||(e.autoPreventDefault=!1)}var i={options:{padding:.1,forceCanvas:!1,doubleBuffering:!1,resolution:d.Browser.retina?2:1,projectionZoom:function(e){var t=e.getMaxZoom(),e=e.getMinZoom();return t===1/0?e+8:(t+e)/2},destroyInteractionManager:!1,autoPreventDefault:!0,preserveDrawingBuffer:!1,clearBeforeRender:!0,shouldRedrawOnMove:function(){return!1}},initialize:function(e,t,i){d.setOptions(this,i),d.stamp(this),this._drawCallback=e,this._pixiContainer=t,this._rendererOptions={resolution:this.options.resolution,antialias:!0,forceCanvas:this.options.forceCanvas,preserveDrawingBuffer:this.options.preserveDrawingBuffer,clearBeforeRender:this.options.clearBeforeRender},o.VERSION<"6"?this._rendererOptions.transparent=!0:this._rendererOptions.backgroundAlpha=0,this._doubleBuffering=o.utils.isWebGLSupported()&&!this.options.forceCanvas&&this.options.doubleBuffering},_setMap:function(){},_setContainerStyle:function(){},_addContainer:function(){this.getPane().appendChild(this._container)},_setEvents:function(){},onAdd:function(e){this._setMap(e),this._container||((e=this._container=d.DomUtil.create("div","leaflet-pixi-overlay")).style.position="absolute",this._renderer=o.autoDetectRenderer(this._rendererOptions),r(this._renderer,this.options.destroyInteractionManager,this.options.autoPreventDefault),e.appendChild(this._renderer.view),this._zoomAnimated&&(d.DomUtil.addClass(e,"leaflet-zoom-animated"),this._setContainerStyle()),this._doubleBuffering&&(this._auxRenderer=o.autoDetectRenderer(this._rendererOptions),r(this._auxRenderer,this.options.destroyInteractionManager,this.options.autoPreventDefault),e.appendChild(this._auxRenderer.view),this._renderer.view.style.position="absolute",this._auxRenderer.view.style.position="absolute")),this._addContainer(),this._setEvents();var i=this._map,n=(this._initialZoom=this.options.projectionZoom(i),this._wgsOrigin=d.latLng([0,0]),this._wgsInitialShift=i.project(this._wgsOrigin,this._initialZoom),this._mapInitialZoom=i.getZoom(),this);this.utils={latLngToLayerPoint:function(e,t){return t=void 0===t?n._initialZoom:t,i.project(d.latLng(e),t)},layerPointToLatLng:function(e,t){t=void 0===t?n._initialZoom:t;e=d.point(e);return i.unproject(e,t)},getScale:function(e){return void 0===e?i.getZoomScale(i.getZoom(),n._initialZoom):i.getZoomScale(e,n._initialZoom)},getRenderer:function(){return n._renderer},getContainer:function(){return n._pixiContainer},getMap:function(){return n._map}},this._update({type:"add"})},onRemove:function(){d.DomUtil.remove(this._container)},getEvents:function(){var e={zoom:this._onZoom,move:this._onMove,moveend:this._update};return this._zoomAnimated&&(e.zoomanim=this._onAnimZoom),e},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_onAnimZoom:function(e){this._updateTransform(e.center,e.zoom)},_onMove:function(e){this.options.shouldRedrawOnMove(e)&&this._update(e)},_updateTransform:function(e,t){var i=this._map.getZoomScale(t,this._zoom),n=this._map.getSize().multiplyBy(.5+this.options.padding),o=this._map.project(this._center,t),n=n.multiplyBy(-i).add(o).subtract(this._map._getNewPixelOrigin(e,t));d.Browser.any3d?d.DomUtil.setTransform(this._container,n,i):d.DomUtil.setPosition(this._container,n)},_redraw:function(e,t){this._disableLeafletRounding();var i=this._map.getZoomScale(this._zoom,this._initialZoom),e=this._map.latLngToLayerPoint(this._wgsOrigin)._subtract(this._wgsInitialShift.multiplyBy(i))._subtract(e);this._pixiContainer.scale.set(i),this._pixiContainer.position.set(e.x,e.y),this._drawCallback(this.utils,t),this._enableLeafletRounding()},_update:function(e){var t,i,n,o,r,s,a;this._map._animatingZoom&&this._bounds||(r=this.options.padding,o=this._map.getSize(),s=this._map.containerPointToLayerPoint(o.multiplyBy(-r)).round(),this._bounds=new d.Bounds(s,s.add(o.multiplyBy(1+2*r)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom(),this._doubleBuffering&&(s=this._renderer,this._renderer=this._auxRenderer,this._auxRenderer=s),t=this._renderer.view,i=this._bounds,n=this._container,o=i.getSize(),this._renderer.size&&this._renderer.size.x===o.x&&this._renderer.size.y===o.y||(this._renderer.gl&&(this._renderer.resolution=this.options.resolution,this._renderer.rootRenderTarget)&&(this._renderer.rootRenderTarget.resolution=this.options.resolution),this._renderer.resize(o.x,o.y),t.style.width=o.x+"px",t.style.height=o.y+"px",this._renderer.gl&&(r=this._renderer.gl).drawingBufferWidth!==this._renderer.width&&(s=this.options.resolution*r.drawingBufferWidth/this._renderer.width,this._renderer.resolution=s,this._renderer.rootRenderTarget&&(this._renderer.rootRenderTarget.resolution=s),this._renderer.resize(o.x,o.y)),this._renderer.size=o),this._doubleBuffering?(a=this,requestAnimationFrame(function(){a._redraw(i.min,e),a._renderer.gl.finish(),t.style.visibility="visible",a._auxRenderer.view.style.visibility="hidden",d.DomUtil.setPosition(n,i.min)})):(this._redraw(i.min,e),d.DomUtil.setPosition(n,i.min)))},_disableLeafletRounding:function(){d.Point.prototype._round=e},_enableLeafletRounding:function(){d.Point.prototype._round=t},redraw:function(e){return this._map&&(this._disableLeafletRounding(),this._drawCallback(this.utils,e),this._enableLeafletRounding()),this},_destroy:function(){this._renderer.destroy(!0),this._doubleBuffering&&this._auxRenderer.destroy(!0)},destroy:function(){this.remove(),this._destroy()}};"1"<=d.version?d.PixiOverlay=d.Layer.extend(i):(d.Map.prototype.getZoomScale=function(e,t){var i=this.options.crs;return t=void 0===t?this._zoom:t,i.scale(e)/i.scale(t)},d.DomUtil.setTransform=function(e,t,i){t=t||new d.Point(0,0);e.style[d.DomUtil.TRANSFORM]=(d.Browser.ie3d?"translate("+t.x+"px,"+t.y+"px)":"translate3d("+t.x+"px,"+t.y+"px,0)")+(i?" scale("+i+")":"")},i.includes=d.Mixin.Events,i.addTo=function(e){return e.addLayer(this),this},i._setMap=function(e){this._map=e,this._zoomAnimated=e._zoomAnimated},i._setContainerStyle=function(){var t=this;["-webkit-transform-origin","-ms-transform-origin","transform-origin"].forEach(function(e){t._container.style[e]="0 0"})},i._addContainer=function(){this._map.getPanes()[this.options.pane||"overlayPane"].appendChild(this._container)},i._setEvents=function(){var e,t=this.getEvents();for(e in t)this._map.on(e,t[e],this)},i.onRemove=function(){this._map=null;var e,t=this._container.parentNode,i=(t&&t.removeChild(this._container),this.getEvents());for(e in i)this._map.off(e,i[e],this)},i.destroy=function(){var e=this._map||this._mapToAdd;e&&e.removeLayer(this),this._destroy()},d.PixiOverlay=d.Class.extend(i)),d.pixiOverlay=function(e,t,i){return d.Browser.canvas?new d.PixiOverlay(e,t,i):null}});
--------------------------------------------------------------------------------
/docs/city-mesh.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | French cities mesh graph-draw demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/docs/railways.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Rail network demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/docs/polyline.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Polyline graph-draw demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/src/graph-draw.js:
--------------------------------------------------------------------------------
1 | // Graph-draw
2 | // version: 2.0.3
3 | // author: Manuel Baclet
4 | // license: MIT
5 |
6 | 'use strict';
7 |
8 | var tess = new (require('./tessellator'))();
9 |
10 | /* Stack class with no duplicates */
11 |
12 | function TaskList() {
13 | this.list = [];
14 | this.hash = Object.create(null);
15 | }
16 | TaskList.prototype.push = function(index) {
17 | if (this.hash[index]) return;
18 | this.hash[index] = true;
19 | this.list.push(index);
20 | };
21 | TaskList.prototype.pop = function() {
22 | if (this.list.length) {
23 | var value = this.list.pop();
24 | delete this.hash[value];
25 | return value;
26 | } else return null;
27 | };
28 |
29 | /* Union-find functions */
30 |
31 | function unionFindUnion(obj1, obj2) {
32 | var root1 = unionFindFind(obj1);
33 | var root2 = unionFindFind(obj2);
34 | if (root1 !== root2) {
35 | if (root1.rank < root2.rank) {
36 | root1.parent = root2;
37 | } else {
38 | root2.parent = root1;
39 | if (root1.rank === root2.rank) root1.rank += 1;
40 | }
41 | }
42 | }
43 |
44 | function unionFindFind(x) {
45 | if (x.parent !== x) x.parent = unionFindFind(x.parent);
46 | return x.parent;
47 | }
48 |
49 | /* Fast collision test for rectangles*/
50 |
51 | function subTest(p1, p2 , r) {
52 | var position;
53 | var norm = norm2(p1, p2);
54 | var product, localPosition;
55 | for (var i = 0; i < 4; i++) {
56 | product = innerProduct(p1, p2, p1, r[i]);
57 | if (product <= 0) localPosition = -1;
58 | else if (product >= norm) localPosition = 1;
59 | else return false;
60 | if (i === 0) {
61 | position = localPosition;
62 | } else {
63 | if (localPosition !== position) {
64 | return false;
65 | }
66 | }
67 | }
68 | return true;
69 | }
70 |
71 | function rectanglesCollide(r1, r2) {
72 | return !(subTest(r1[0], r1[1], r2) || subTest(r1[1], r1[2], r2) || subTest(r2[0], r2[1], r1) || subTest(r2[1], r2[2], r1));
73 | }
74 |
75 | /* Geometry helpers */
76 |
77 | function innerProduct(p1, p2, p3, p4) {
78 | return (p2[0] - p1[0]) * (p4[0] - p3[0]) + (p2[1] - p1[1]) * (p4[1] - p3[1]);
79 | }
80 | function norm2(p1, p2) {
81 | var dx = p2[0] - p1[0];
82 | var dy = p2[1] - p1[1];
83 | return dx * dx + dy * dy;
84 | }
85 |
86 | function segmentIntersection(p1, p2, p3, p4) {
87 | // find intersection point of [p1, p2] and [p3, p4], supposing it exists
88 | var dx = p2[0] - p1[0];
89 | var dy = p2[1] - p1[1];
90 | var dx2 = p4[0] - p3[0];
91 | var dy2 = p4[1] - p3[1];
92 | var lambda = ((p2[0] - p3[0]) * dy - dx * (p2[1] - p3[1])) /
93 | (dx2 * dy - dx * dy2);
94 | return [p3[0] + lambda * dx2, p3[1] + lambda * dy2];
95 | }
96 | function rayIntersection(p1, p2, p3, p4) {
97 | // find intersection point of (p1, p2] and (p3, p4]
98 | var dx = p2[0] - p1[0];
99 | var dy = p2[1] - p1[1];
100 | var dx2 = p4[0] - p3[0];
101 | var dy2 = p4[1] - p3[1];
102 | var denom = dx2 * dy - dx * dy2;
103 | if (denom === 0) return {point: p1, valid: true};
104 | var lambda = ((p2[0] - p3[0]) * dy - dx * (p2[1] - p3[1])) / denom;
105 | var inter = [p3[0] + lambda * dx2, p3[1] + lambda * dy2];
106 | if (lambda > 1 || innerProduct(p2, inter, p2, p1) < 0) return {point: inter, valid: false};
107 | else return {point: inter, valid: true};
108 | }
109 |
110 | function getData(from, to, w) {
111 | var ux = to[0] - from[0];
112 | var uy = to[1] - from [1];
113 | var Nu = Math.sqrt(ux * ux + uy * uy);
114 | var theta = Math.acos(ux / Nu);
115 | if (uy < 0) theta *= -1;
116 | return {
117 | angle: theta,
118 | norm: Nu,
119 | dir: [ux, uy],
120 | ortho: [- w * uy / Nu, w * ux / Nu]
121 | };
122 | }
123 |
124 | /* main function */
125 |
126 | function graphDraw(graph, width, cb, maxAngle) {
127 | var w = width / 2;
128 | maxAngle = Math.max(Math.PI, maxAngle || 2 * Math.PI);
129 |
130 | /* Data structures setup */
131 |
132 | var vertices = graph.vertices.map(function(coords) {
133 | return {
134 | coords: coords,
135 | neighList: []
136 | };
137 | });
138 | var edges = graph.edges.map(function(edge, index) {
139 | var from = edge[0];
140 | var to = edge[1];
141 | var vertexFrom = vertices[from];
142 | var vertexTo = vertices[to];
143 | var data = getData(vertexFrom.coords, vertexTo.coords, w);
144 | vertexFrom.neighList.push({
145 | to: to,
146 | angle: data.angle,
147 | dir: data.dir,
148 | ortho: data.ortho,
149 | index: index
150 | });
151 | vertexTo.neighList.push({
152 | to: from,
153 | angle: data.angle <= 0 ? data.angle + Math.PI : data.angle - Math.PI,
154 | dir: [-data.dir[0], -data.dir[1]],
155 | ortho: [-data.ortho[0], -data.ortho[1]],
156 | index: index
157 | });
158 | var obj = {
159 | rank: 0,
160 | edge: edge,
161 | points: {}
162 | };
163 | obj.points[to] = {};
164 | obj.points[from] = {};
165 | obj.parent = obj;
166 | return obj;
167 | });
168 |
169 | /* Build edges contour points */
170 |
171 | var toPostProcess = [];
172 | vertices.forEach(function(vertex, vindex) {
173 | var point = vertex.coords;
174 | var prepared = vertex.neighList;
175 | prepared.sort(function(a, b) {return a.angle - b.angle;});
176 | var n = prepared.length;
177 | if (n === 1) {
178 | var edge = prepared[0];
179 | var p1 = [point[0] + edge.ortho[0], point[1] + edge.ortho[1]];
180 | var p2 = [point[0] - edge.ortho[0], point[1] - edge.ortho[1]];
181 | var edgePoints = edges[edge.index].points;
182 | edgePoints[vindex].first_vertex = edge.index;
183 | edgePoints[vindex].last_vertex = edge.index;
184 | edgePoints[vindex].first = p1;
185 | edgePoints[vindex].remove_middle_first = true;
186 | edgePoints[vindex].remove_middle_last = true;
187 | edgePoints[vindex].last = p2;
188 | } else {
189 | prepared.forEach(function(edge, index) {
190 | var last = (index === n - 1);
191 | var next = prepared[last ? 0 : index + 1];
192 | var edgePoints = edges[edge.index].points;
193 | var nextPoints = edges[next.index].points;
194 | edgePoints[vindex].first_vertex = next.index;
195 | nextPoints[vindex].last_vertex = edge.index;
196 | var p1 = [point[0] + edge.ortho[0], point[1] + edge.ortho[1]];
197 | var p2 = [p1[0] + edge.dir[0], p1[1] + edge.dir[1]];
198 | var p3 = [point[0] - next.ortho[0], point[1] - next.ortho[1]];
199 | var p4 = [p3[0] + next.dir[0], p3[1] + next.dir[1]];
200 | var intersection = rayIntersection(p1, p2, p3, p4);
201 | var newPoint = intersection.point;
202 | if (intersection.valid) {
203 | var nextAngle = last ? next.angle + 2 * Math.PI : next.angle;
204 | if (nextAngle - edge.angle > maxAngle) {
205 | edgePoints[vindex].first = p1;
206 | nextPoints[vindex].last = p3;
207 | var vec = [newPoint[0] - point[0], newPoint[1] - point[1]];
208 | var invNorm = 1 / Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1]);
209 | edgePoints[vindex].miter_first = nextPoints[vindex].miter_last = [
210 | point[0] + w * vec[0] * invNorm,
211 | point[1] + w * vec[1] * invNorm
212 | ];
213 | } else {
214 | edgePoints[vindex].first = newPoint;
215 | nextPoints[vindex].last = newPoint;
216 | }
217 | if (n === 2) {
218 | edgePoints[vindex].remove_middle_first = true;
219 | nextPoints[vindex].remove_middle_last = true;
220 | }
221 | } else {
222 | var q1 = [newPoint[0] - edge.ortho[0], newPoint[1] - edge.ortho[1]];
223 | var q3 = [newPoint[0] + next.ortho[0], newPoint[1] + next.ortho[1]];
224 |
225 | toPostProcess.push({
226 | done: [edge.index, next.index],
227 | todo: [vindex, edge.to, next.to],
228 | rectangles: [[p1, newPoint, q1, point], [p3, newPoint, q3, point]]
229 | });
230 |
231 | edgePoints[vindex].first = p1;
232 | nextPoints[vindex].last = p3;
233 | }
234 | });
235 | }
236 | });
237 |
238 | /* Build each edge polygon */
239 |
240 | edges.forEach(function(obj) {
241 | var edge = obj.edge;
242 | var from = edge[0];
243 | var to = edge[1];
244 | var obj1 = obj.points[from];
245 | var obj2 = obj.points[to];
246 | var fromCoords = vertices[from].coords;
247 | var toCoords = vertices[to].coords;
248 | var newPoly = obj.polygon = [];
249 |
250 | if (innerProduct(obj1.last, obj2.first, fromCoords, toCoords) < 0) {
251 | var i1 = obj1.last_vertex;
252 | var i2 = obj2.first_vertex;
253 | unionFindUnion(edges[i1], edges[i2]);
254 | newPoly.push(segmentIntersection(obj1.miter_last || fromCoords, obj1.last, obj2.first, obj2.miter_first || toCoords));
255 | } else {
256 | newPoly.push(obj1.last, obj2.first);
257 | }
258 | if (obj2.miter_first) newPoly.push(obj2.miter_first);
259 | if (!(obj2.remove_middle_first && obj2.remove_middle_last)) newPoly.push(toCoords);
260 | if (obj2.miter_last) newPoly.push(obj2.miter_last);
261 | if (innerProduct(obj1.first, obj2.last, fromCoords, toCoords) < 0) {
262 | var i1 = obj1.first_vertex;
263 | var i2 = obj2.last_vertex;
264 | unionFindUnion(edges[i1], edges[i2]);
265 | newPoly.push(segmentIntersection(obj1.first, obj1.miter_first || fromCoords, obj2.miter_last || toCoords, obj2.last));
266 | } else {
267 | newPoly.push(obj2.last, obj1.first);
268 | }
269 | if (obj1.miter_first) newPoly.push(obj1.miter_first);
270 | if (!(obj1.remove_middle_first && obj1.remove_middle_last)) newPoly.push(fromCoords);
271 | if (obj1.miter_last) newPoly.push(obj1.miter_last);
272 | });
273 |
274 | /* Find locally overlapping edges */
275 |
276 | var shapeMemo = Object.create(null);
277 |
278 | toPostProcess.forEach(function(obj) {
279 | var done = Object.create(null);
280 | var i1 = obj.done[0];
281 | var i2 = obj.done[1];
282 | var e1 = edges[i1];
283 | var e2 = edges[i2];
284 | unionFindUnion(e1, e2);
285 | done[i1] = true;
286 | done[i2] = true;
287 | var todo = new TaskList();
288 | obj.todo.forEach(function(vertex) {
289 | todo.push(vertex);
290 | });
291 | var from;
292 | var r1 = obj.rectangles[0];
293 | var r2 = obj.rectangles[1];
294 | while((from = todo.pop()) !== null) {
295 | vertices[from].neighList.forEach(function(neigh) {
296 | var index = neigh.index;
297 | if (done[index]) return;
298 | var to = neigh.to;
299 | var rectangle = shapeMemo[index];
300 | if (!rectangle) {
301 | var fromCoords = vertices[from].coords;
302 | var toCoords = vertices[to].coords;
303 | var p1 = [fromCoords[0] + neigh.ortho[0], fromCoords[1] + neigh.ortho[1]];
304 | var p2 = [toCoords[0] + neigh.ortho[0], toCoords[1] + neigh.ortho[1]];
305 | var p3 = [toCoords[0] - neigh.ortho[0], toCoords[1] - neigh.ortho[1]];
306 | var p4 = [fromCoords[0] - neigh.ortho[0], fromCoords[1] - neigh.ortho[1]];
307 | rectangle = shapeMemo[index] = [p1, p2, p3, p4];
308 | }
309 | done[index] = true;
310 | if (rectanglesCollide(rectangle, r1) || rectanglesCollide(rectangle, r2)) {
311 | unionFindUnion(e1, edges[index]);
312 | todo.push(to);
313 | }
314 | });
315 | }
316 | });
317 |
318 | /* Execute cb on each polygon */
319 |
320 | var needUnion = [];
321 | edges.forEach(function(obj, index) {
322 | if (obj.rank > 0 && obj.parent === obj) {
323 | obj.union = obj.union || [];
324 | obj.union.push(obj.polygon);
325 | needUnion.push(index);
326 | } else {
327 | if (obj.parent === obj) {
328 | cb(obj.polygon);
329 | } else {
330 | var root = unionFindFind(obj);
331 | root.union = root.union || [];
332 | root.union.push(obj.polygon);
333 | }
334 | }
335 | });
336 |
337 | tess._cb = cb;
338 | needUnion.forEach(function(index) {
339 | tess.run(edges[index].union);
340 | });
341 | delete tess._cb;
342 | }
343 |
344 | module.exports = graphDraw;
345 |
--------------------------------------------------------------------------------
/docs/css/leaflet.css:
--------------------------------------------------------------------------------
1 | /* required styles */
2 |
3 | .leaflet-pane,
4 | .leaflet-tile,
5 | .leaflet-marker-icon,
6 | .leaflet-marker-shadow,
7 | .leaflet-tile-container,
8 | .leaflet-pane > svg,
9 | .leaflet-pane > canvas,
10 | .leaflet-zoom-box,
11 | .leaflet-image-layer,
12 | .leaflet-layer {
13 | position: absolute;
14 | left: 0;
15 | top: 0;
16 | }
17 | .leaflet-container {
18 | overflow: hidden;
19 | }
20 | .leaflet-tile,
21 | .leaflet-marker-icon,
22 | .leaflet-marker-shadow {
23 | -webkit-user-select: none;
24 | -moz-user-select: none;
25 | user-select: none;
26 | -webkit-user-drag: none;
27 | }
28 | /* Prevents IE11 from highlighting tiles in blue */
29 | .leaflet-tile::selection {
30 | background: transparent;
31 | }
32 | /* Safari renders non-retina tile on retina better with this, but Chrome is worse */
33 | .leaflet-safari .leaflet-tile {
34 | image-rendering: -webkit-optimize-contrast;
35 | }
36 | /* hack that prevents hw layers "stretching" when loading new tiles */
37 | .leaflet-safari .leaflet-tile-container {
38 | width: 1600px;
39 | height: 1600px;
40 | -webkit-transform-origin: 0 0;
41 | }
42 | .leaflet-marker-icon,
43 | .leaflet-marker-shadow {
44 | display: block;
45 | }
46 | /* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
47 | /* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
48 | .leaflet-container .leaflet-overlay-pane svg,
49 | .leaflet-container .leaflet-marker-pane img,
50 | .leaflet-container .leaflet-shadow-pane img,
51 | .leaflet-container .leaflet-tile-pane img,
52 | .leaflet-container img.leaflet-image-layer,
53 | .leaflet-container .leaflet-tile {
54 | max-width: none !important;
55 | max-height: none !important;
56 | }
57 |
58 | .leaflet-container.leaflet-touch-zoom {
59 | -ms-touch-action: pan-x pan-y;
60 | touch-action: pan-x pan-y;
61 | }
62 | .leaflet-container.leaflet-touch-drag {
63 | -ms-touch-action: pinch-zoom;
64 | /* Fallback for FF which doesn't support pinch-zoom */
65 | touch-action: none;
66 | touch-action: pinch-zoom;
67 | }
68 | .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
69 | -ms-touch-action: none;
70 | touch-action: none;
71 | }
72 | .leaflet-container {
73 | -webkit-tap-highlight-color: transparent;
74 | }
75 | .leaflet-container a {
76 | -webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
77 | }
78 | .leaflet-tile {
79 | filter: inherit;
80 | visibility: hidden;
81 | }
82 | .leaflet-tile-loaded {
83 | visibility: inherit;
84 | }
85 | .leaflet-zoom-box {
86 | width: 0;
87 | height: 0;
88 | -moz-box-sizing: border-box;
89 | box-sizing: border-box;
90 | z-index: 800;
91 | }
92 | /* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
93 | .leaflet-overlay-pane svg {
94 | -moz-user-select: none;
95 | }
96 |
97 | .leaflet-pane { z-index: 400; }
98 |
99 | .leaflet-tile-pane { z-index: 200; }
100 | .leaflet-overlay-pane { z-index: 400; }
101 | .leaflet-shadow-pane { z-index: 500; }
102 | .leaflet-marker-pane { z-index: 600; }
103 | .leaflet-tooltip-pane { z-index: 650; }
104 | .leaflet-popup-pane { z-index: 700; }
105 |
106 | .leaflet-map-pane canvas { z-index: 100; }
107 | .leaflet-map-pane svg { z-index: 200; }
108 |
109 | .leaflet-vml-shape {
110 | width: 1px;
111 | height: 1px;
112 | }
113 | .lvml {
114 | behavior: url(#default#VML);
115 | display: inline-block;
116 | position: absolute;
117 | }
118 |
119 |
120 | /* control positioning */
121 |
122 | .leaflet-control {
123 | position: relative;
124 | z-index: 800;
125 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
126 | pointer-events: auto;
127 | }
128 | .leaflet-top,
129 | .leaflet-bottom {
130 | position: absolute;
131 | z-index: 1000;
132 | pointer-events: none;
133 | }
134 | .leaflet-top {
135 | top: 0;
136 | }
137 | .leaflet-right {
138 | right: 0;
139 | }
140 | .leaflet-bottom {
141 | bottom: 0;
142 | }
143 | .leaflet-left {
144 | left: 0;
145 | }
146 | .leaflet-control {
147 | float: left;
148 | clear: both;
149 | }
150 | .leaflet-right .leaflet-control {
151 | float: right;
152 | }
153 | .leaflet-top .leaflet-control {
154 | margin-top: 10px;
155 | }
156 | .leaflet-bottom .leaflet-control {
157 | margin-bottom: 10px;
158 | }
159 | .leaflet-left .leaflet-control {
160 | margin-left: 10px;
161 | }
162 | .leaflet-right .leaflet-control {
163 | margin-right: 10px;
164 | }
165 |
166 |
167 | /* zoom and fade animations */
168 |
169 | .leaflet-fade-anim .leaflet-tile {
170 | will-change: opacity;
171 | }
172 | .leaflet-fade-anim .leaflet-popup {
173 | opacity: 0;
174 | -webkit-transition: opacity 0.2s linear;
175 | -moz-transition: opacity 0.2s linear;
176 | transition: opacity 0.2s linear;
177 | }
178 | .leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
179 | opacity: 1;
180 | }
181 | .leaflet-zoom-animated {
182 | -webkit-transform-origin: 0 0;
183 | -ms-transform-origin: 0 0;
184 | transform-origin: 0 0;
185 | }
186 | .leaflet-zoom-anim .leaflet-zoom-animated {
187 | will-change: transform;
188 | }
189 | .leaflet-zoom-anim .leaflet-zoom-animated {
190 | -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
191 | -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
192 | transition: transform 0.25s cubic-bezier(0,0,0.25,1);
193 | }
194 | .leaflet-zoom-anim .leaflet-tile,
195 | .leaflet-pan-anim .leaflet-tile {
196 | -webkit-transition: none;
197 | -moz-transition: none;
198 | transition: none;
199 | }
200 |
201 | .leaflet-zoom-anim .leaflet-zoom-hide {
202 | visibility: hidden;
203 | }
204 |
205 |
206 | /* cursors */
207 |
208 | .leaflet-interactive {
209 | cursor: pointer;
210 | }
211 | .leaflet-grab {
212 | cursor: -webkit-grab;
213 | cursor: -moz-grab;
214 | cursor: grab;
215 | }
216 | .leaflet-crosshair,
217 | .leaflet-crosshair .leaflet-interactive {
218 | cursor: crosshair;
219 | }
220 | .leaflet-popup-pane,
221 | .leaflet-control {
222 | cursor: auto;
223 | }
224 | .leaflet-dragging .leaflet-grab,
225 | .leaflet-dragging .leaflet-grab .leaflet-interactive,
226 | .leaflet-dragging .leaflet-marker-draggable {
227 | cursor: move;
228 | cursor: -webkit-grabbing;
229 | cursor: -moz-grabbing;
230 | cursor: grabbing;
231 | }
232 |
233 | /* marker & overlays interactivity */
234 | .leaflet-marker-icon,
235 | .leaflet-marker-shadow,
236 | .leaflet-image-layer,
237 | .leaflet-pane > svg path,
238 | .leaflet-tile-container {
239 | pointer-events: none;
240 | }
241 |
242 | .leaflet-marker-icon.leaflet-interactive,
243 | .leaflet-image-layer.leaflet-interactive,
244 | .leaflet-pane > svg path.leaflet-interactive,
245 | svg.leaflet-image-layer.leaflet-interactive path {
246 | pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
247 | pointer-events: auto;
248 | }
249 |
250 | /* visual tweaks */
251 |
252 | .leaflet-container {
253 | background: #ddd;
254 | outline: 0;
255 | }
256 | .leaflet-container a {
257 | color: #0078A8;
258 | }
259 | .leaflet-container a.leaflet-active {
260 | outline: 2px solid orange;
261 | }
262 | .leaflet-zoom-box {
263 | border: 2px dotted #38f;
264 | background: rgba(255,255,255,0.5);
265 | }
266 |
267 |
268 | /* general typography */
269 | .leaflet-container {
270 | font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
271 | }
272 |
273 |
274 | /* general toolbar styles */
275 |
276 | .leaflet-bar {
277 | box-shadow: 0 1px 5px rgba(0,0,0,0.65);
278 | border-radius: 4px;
279 | }
280 | .leaflet-bar a,
281 | .leaflet-bar a:hover {
282 | background-color: #fff;
283 | border-bottom: 1px solid #ccc;
284 | width: 26px;
285 | height: 26px;
286 | line-height: 26px;
287 | display: block;
288 | text-align: center;
289 | text-decoration: none;
290 | color: black;
291 | }
292 | .leaflet-bar a,
293 | .leaflet-control-layers-toggle {
294 | background-position: 50% 50%;
295 | background-repeat: no-repeat;
296 | display: block;
297 | }
298 | .leaflet-bar a:hover {
299 | background-color: #f4f4f4;
300 | }
301 | .leaflet-bar a:first-child {
302 | border-top-left-radius: 4px;
303 | border-top-right-radius: 4px;
304 | }
305 | .leaflet-bar a:last-child {
306 | border-bottom-left-radius: 4px;
307 | border-bottom-right-radius: 4px;
308 | border-bottom: none;
309 | }
310 | .leaflet-bar a.leaflet-disabled {
311 | cursor: default;
312 | background-color: #f4f4f4;
313 | color: #bbb;
314 | }
315 |
316 | .leaflet-touch .leaflet-bar a {
317 | width: 30px;
318 | height: 30px;
319 | line-height: 30px;
320 | }
321 | .leaflet-touch .leaflet-bar a:first-child {
322 | border-top-left-radius: 2px;
323 | border-top-right-radius: 2px;
324 | }
325 | .leaflet-touch .leaflet-bar a:last-child {
326 | border-bottom-left-radius: 2px;
327 | border-bottom-right-radius: 2px;
328 | }
329 |
330 | /* zoom control */
331 |
332 | .leaflet-control-zoom-in,
333 | .leaflet-control-zoom-out {
334 | font: bold 18px 'Lucida Console', Monaco, monospace;
335 | text-indent: 1px;
336 | }
337 |
338 | .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
339 | font-size: 22px;
340 | }
341 |
342 |
343 | /* layers control */
344 |
345 | .leaflet-control-layers {
346 | box-shadow: 0 1px 5px rgba(0,0,0,0.4);
347 | background: #fff;
348 | border-radius: 5px;
349 | }
350 | .leaflet-control-layers-toggle {
351 | background-image: url(images/layers.png);
352 | width: 36px;
353 | height: 36px;
354 | }
355 | .leaflet-retina .leaflet-control-layers-toggle {
356 | background-image: url(images/layers-2x.png);
357 | background-size: 26px 26px;
358 | }
359 | .leaflet-touch .leaflet-control-layers-toggle {
360 | width: 44px;
361 | height: 44px;
362 | }
363 | .leaflet-control-layers .leaflet-control-layers-list,
364 | .leaflet-control-layers-expanded .leaflet-control-layers-toggle {
365 | display: none;
366 | }
367 | .leaflet-control-layers-expanded .leaflet-control-layers-list {
368 | display: block;
369 | position: relative;
370 | }
371 | .leaflet-control-layers-expanded {
372 | padding: 6px 10px 6px 6px;
373 | color: #333;
374 | background: #fff;
375 | }
376 | .leaflet-control-layers-scrollbar {
377 | overflow-y: scroll;
378 | overflow-x: hidden;
379 | padding-right: 5px;
380 | }
381 | .leaflet-control-layers-selector {
382 | margin-top: 2px;
383 | position: relative;
384 | top: 1px;
385 | }
386 | .leaflet-control-layers label {
387 | display: block;
388 | }
389 | .leaflet-control-layers-separator {
390 | height: 0;
391 | border-top: 1px solid #ddd;
392 | margin: 5px -10px 5px -6px;
393 | }
394 |
395 | /* Default icon URLs */
396 | .leaflet-default-icon-path {
397 | background-image: url(images/marker-icon.png);
398 | }
399 |
400 |
401 | /* attribution and scale controls */
402 |
403 | .leaflet-container .leaflet-control-attribution {
404 | background: #fff;
405 | background: rgba(255, 255, 255, 0.7);
406 | margin: 0;
407 | }
408 | .leaflet-control-attribution,
409 | .leaflet-control-scale-line {
410 | padding: 0 5px;
411 | color: #333;
412 | }
413 | .leaflet-control-attribution a {
414 | text-decoration: none;
415 | }
416 | .leaflet-control-attribution a:hover {
417 | text-decoration: underline;
418 | }
419 | .leaflet-container .leaflet-control-attribution,
420 | .leaflet-container .leaflet-control-scale {
421 | font-size: 11px;
422 | }
423 | .leaflet-left .leaflet-control-scale {
424 | margin-left: 5px;
425 | }
426 | .leaflet-bottom .leaflet-control-scale {
427 | margin-bottom: 5px;
428 | }
429 | .leaflet-control-scale-line {
430 | border: 2px solid #777;
431 | border-top: none;
432 | line-height: 1.1;
433 | padding: 2px 5px 1px;
434 | font-size: 11px;
435 | white-space: nowrap;
436 | overflow: hidden;
437 | -moz-box-sizing: border-box;
438 | box-sizing: border-box;
439 |
440 | background: #fff;
441 | background: rgba(255, 255, 255, 0.5);
442 | }
443 | .leaflet-control-scale-line:not(:first-child) {
444 | border-top: 2px solid #777;
445 | border-bottom: none;
446 | margin-top: -2px;
447 | }
448 | .leaflet-control-scale-line:not(:first-child):not(:last-child) {
449 | border-bottom: 2px solid #777;
450 | }
451 |
452 | .leaflet-touch .leaflet-control-attribution,
453 | .leaflet-touch .leaflet-control-layers,
454 | .leaflet-touch .leaflet-bar {
455 | box-shadow: none;
456 | }
457 | .leaflet-touch .leaflet-control-layers,
458 | .leaflet-touch .leaflet-bar {
459 | border: 2px solid rgba(0,0,0,0.2);
460 | background-clip: padding-box;
461 | }
462 |
463 |
464 | /* popup */
465 |
466 | .leaflet-popup {
467 | position: absolute;
468 | text-align: center;
469 | margin-bottom: 20px;
470 | }
471 | .leaflet-popup-content-wrapper {
472 | padding: 1px;
473 | text-align: left;
474 | border-radius: 12px;
475 | }
476 | .leaflet-popup-content {
477 | margin: 13px 19px;
478 | line-height: 1.4;
479 | }
480 | .leaflet-popup-content p {
481 | margin: 18px 0;
482 | }
483 | .leaflet-popup-tip-container {
484 | width: 40px;
485 | height: 20px;
486 | position: absolute;
487 | left: 50%;
488 | margin-left: -20px;
489 | overflow: hidden;
490 | pointer-events: none;
491 | }
492 | .leaflet-popup-tip {
493 | width: 17px;
494 | height: 17px;
495 | padding: 1px;
496 |
497 | margin: -10px auto 0;
498 |
499 | -webkit-transform: rotate(45deg);
500 | -moz-transform: rotate(45deg);
501 | -ms-transform: rotate(45deg);
502 | transform: rotate(45deg);
503 | }
504 | .leaflet-popup-content-wrapper,
505 | .leaflet-popup-tip {
506 | background: white;
507 | color: #333;
508 | box-shadow: 0 3px 14px rgba(0,0,0,0.4);
509 | }
510 | .leaflet-container a.leaflet-popup-close-button {
511 | position: absolute;
512 | top: 0;
513 | right: 0;
514 | padding: 4px 4px 0 0;
515 | border: none;
516 | text-align: center;
517 | width: 18px;
518 | height: 14px;
519 | font: 16px/14px Tahoma, Verdana, sans-serif;
520 | color: #c3c3c3;
521 | text-decoration: none;
522 | font-weight: bold;
523 | background: transparent;
524 | }
525 | .leaflet-container a.leaflet-popup-close-button:hover {
526 | color: #999;
527 | }
528 | .leaflet-popup-scrolled {
529 | overflow: auto;
530 | border-bottom: 1px solid #ddd;
531 | border-top: 1px solid #ddd;
532 | }
533 |
534 | .leaflet-oldie .leaflet-popup-content-wrapper {
535 | -ms-zoom: 1;
536 | }
537 | .leaflet-oldie .leaflet-popup-tip {
538 | width: 24px;
539 | margin: 0 auto;
540 |
541 | -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
542 | filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
543 | }
544 | .leaflet-oldie .leaflet-popup-tip-container {
545 | margin-top: -1px;
546 | }
547 |
548 | .leaflet-oldie .leaflet-control-zoom,
549 | .leaflet-oldie .leaflet-control-layers,
550 | .leaflet-oldie .leaflet-popup-content-wrapper,
551 | .leaflet-oldie .leaflet-popup-tip {
552 | border: 1px solid #999;
553 | }
554 |
555 |
556 | /* div icon */
557 |
558 | .leaflet-div-icon {
559 | background: #fff;
560 | border: 1px solid #666;
561 | }
562 |
563 |
564 | /* Tooltip */
565 | /* Base styles for the element that has a tooltip */
566 | .leaflet-tooltip {
567 | position: absolute;
568 | padding: 6px;
569 | background-color: #fff;
570 | border: 1px solid #fff;
571 | border-radius: 3px;
572 | color: #222;
573 | white-space: nowrap;
574 | -webkit-user-select: none;
575 | -moz-user-select: none;
576 | -ms-user-select: none;
577 | user-select: none;
578 | pointer-events: none;
579 | box-shadow: 0 1px 3px rgba(0,0,0,0.4);
580 | }
581 | .leaflet-tooltip.leaflet-clickable {
582 | cursor: pointer;
583 | pointer-events: auto;
584 | }
585 | .leaflet-tooltip-top:before,
586 | .leaflet-tooltip-bottom:before,
587 | .leaflet-tooltip-left:before,
588 | .leaflet-tooltip-right:before {
589 | position: absolute;
590 | pointer-events: none;
591 | border: 6px solid transparent;
592 | background: transparent;
593 | content: "";
594 | }
595 |
596 | /* Directions */
597 |
598 | .leaflet-tooltip-bottom {
599 | margin-top: 6px;
600 | }
601 | .leaflet-tooltip-top {
602 | margin-top: -6px;
603 | }
604 | .leaflet-tooltip-bottom:before,
605 | .leaflet-tooltip-top:before {
606 | left: 50%;
607 | margin-left: -6px;
608 | }
609 | .leaflet-tooltip-top:before {
610 | bottom: 0;
611 | margin-bottom: -12px;
612 | border-top-color: #fff;
613 | }
614 | .leaflet-tooltip-bottom:before {
615 | top: 0;
616 | margin-top: -12px;
617 | margin-left: -6px;
618 | border-bottom-color: #fff;
619 | }
620 | .leaflet-tooltip-left {
621 | margin-left: -6px;
622 | }
623 | .leaflet-tooltip-right {
624 | margin-left: 6px;
625 | }
626 | .leaflet-tooltip-left:before,
627 | .leaflet-tooltip-right:before {
628 | top: 50%;
629 | margin-top: -6px;
630 | }
631 | .leaflet-tooltip-left:before {
632 | right: 0;
633 | margin-right: -12px;
634 | border-left-color: #fff;
635 | }
636 | .leaflet-tooltip-right:before {
637 | left: 0;
638 | margin-left: -12px;
639 | border-right-color: #fff;
640 | }
641 |
--------------------------------------------------------------------------------
/docs/js/graph-draw-bundle.min.js:
--------------------------------------------------------------------------------
1 | !function(t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).graphDraw=t()}(function(){return function i(r,n,s){function o(a,t){if(!n[a]){if(!r[a]){var e="function"==typeof require&&require;if(!t&&e)return e(a,!0);if(h)return h(a,!0);throw(t=new Error("Cannot find module '"+a+"'")).code="MODULE_NOT_FOUND",t}e=n[a]={exports:{}},r[a][0].call(e.exports,function(t){return o(r[a][1][t]||t)},e,e.exports,i,r,n,s)}return n[a].exports}for(var h="function"==typeof require&&require,t=0;tMath.abs(t[0])&&(a=1),a=Math.abs(t[2])>Math.abs(t[a])?2:a}var I=4e150;function U(t,a){t.f+=a.f,t.b.f+=a.b.f}function f(t,a,e){return t=t.a,a=a.a,e=e.a,a.b.a===t?e.b.a===t?v(a.a,e.a)?E(e.b.a,a.a,e.a)<=0:0<=E(a.b.a,e.a,a.a):E(e.b.a,t,e.a)<=0:e.b.a===t?0<=E(a.b.a,t,a.a):(a=g(a.b.a,t,a.a),(t=g(e.b.a,t,e.a))<=a)}function w(t){t.a.i=null;var a=t.e;a.a.c=a.c,a.c.a=a.a,t.e=null}function O(t,a){_(t.a),t.c=!1,(t.a=a).i=t}function k(t){for(var a=t.a.a;(t=q(t)).a.a===a;);return t.c&&(O(t,a=y(V(t).a.b,t.a.e)),t=q(t)),t}function z(t,a,e){var i=new j;return i.a=e,i.e=d(t.f,a.e,i),e.i=i}function H(t,a){switch(t.s){case 100130:return 0!=(1&a);case 100131:return 0!==a;case 100132:return 0>1]],o[s[c]])?B:bt)(e,c),o[n]=null,h[n]=e.b,e.b=n}else for(e.c[-(n+1)]=null;0Math.max(s.a,h.a))){if(v(n,s)){if(0>1,h=e[o];if(0==o||v(i[h],i[s])){r[e[n]=s]=n;break}r[e[n]=h]=n,n=o}}function j(){this.e=this.a=null,this.f=0,this.c=this.b=this.h=this.d=!1}function V(t){return t.e.c.b}function q(t){return t.e.a.b}(t=a.prototype).x=function(){R(this,0)},t.B=function(t,a){switch(t){case 100142:return;case 100140:switch(a){case 100130:case 100131:case 100132:case 100133:case 100134:return void(this.s=a)}break;case 100141:return void(this.m=!!a);default:return void h(this,100900)}h(this,100901)},t.y=function(t){switch(t){case 100142:return 0;case 100140:return this.s;case 100141:return this.m;default:h(this,100900)}return!1},t.A=function(t,a,e){this.j[0]=t,this.j[1]=a,this.j[2]=e},t.z=function(t,a){var e=a||null;switch(t){case 100100:case 100106:this.h=e;break;case 100104:case 100110:this.l=e;break;case 100101:case 100107:this.k=e;break;case 100102:case 100108:this.i=e;break;case 100103:case 100109:this.p=e;break;case 100105:case 100111:this.o=e;break;case 100112:this.r=e;break;default:h(this,100900)}},t.C=function(t,a){var e=!1,i=[0,0,0];R(this,2);for(var r=0;r<3;++r){var n=t[r];n<-1e150&&(n=-1e150,e=!0),1e150i[c]&&(i[c]=b,r[c]=e)}if(i[e=i[e=i[1]-o[1]>i[e=0]-o[0]?1:e]-o[e]function(t,a){return v(e[t],e[a])?1:-1})(u.c)),u.e=!0;for(var l=u.b,d=l.a;1<=d;--d)B(l,d);for(l.h=!0,this.f=new tt(this),$(this,-I),$(this,I);null!==(s=st(this.e));){for(;;){t:if(0===(e=this.e).a)n=P(e.b);else if(n=e.c[e.d[e.a-1]],0!==e.b.a&&v(e=P(e.b),n)){n=e;break t}if(null===n||!p(n,s))break;n=st(this.e),D(this,s.c,n.c)}!function t(a,e){for(var i,r=(a.a=e).c;null===r.i;)if((r=r.c)===e.c){var r=a,n=e;(b=new j).a=n.c.b;for(var s=(o=r.f).a;null!==(s=s.a).b&&!o.c(o.b,b,s.b););var o,h,c=V(o=s.b),b=o.a,s=c.a;return void(0===E(b.b.a,n,b.a)?p((b=o.a).a,n)||p(b.b.a,n)||(L(b.b),o.c&&(_(b.c),o.c=!1),G(n.c,b),t(r,n)):(h=v(s.b.a,b.b.a)?o:c,c=void 0,o.d||h.c?(c=h===o?y(n.c.b,b.e):y(s.b.c.b,n.c).b,h.c?O(h,c):((o=z(b=r,o,c)).f=q(o).f+o.a.f,o.d=H(b,o.f)),t(r,n)):A(r,o,n.c,n.c,null,!0)))}o=(b=V(r=k(r.i))).a,(b=x(a,b,null)).c===o?(b=(o=b).c,s=V(r),c=r.a,h=s.a,i=!1,c.b.a!==h.b.a&&Z(a,r),p(c.a,a.a)&&(G(b.b.e,c),b=V(r=k(r)).a,x(a,V(r),s),i=!0),p(h.a,a.a)&&(G(o,h.b.e),o=x(a,s,null),i=!0),i?A(a,r,o.c,b,b,!0):(n=v(h.a,c.a)?h.b.e:c,A(a,r,n=y(o.c.b,n),n.c,n.c,!1),n.b.i.c=!0,J(a,r))):A(a,r,b.c,o,o,!0)}(this,s)}for(this.a=this.f.a.a.b.a.a,s=0;null!==(n=this.f.a.a.b);)n.h||++s,w(n);for(this.f=null,(s=this.e).b=null,s.d=null,this.e=s.c=null,s=this.b,e=s.a.b;e!==s.a;e=n)n=e.b,(e=e.a).e.e===e&&(U(e.c,e),_(e));if(!this.n){if(s=this.b,this.m)for(e=s.b.h;e!==s.b;e=n)n=e.h,e.b.d.c!==e.d.c?e.f=e.d.c?1:-1:_(e);else for(e=s.a.b;e!==s.a;e=n)if(n=e.b,e.c){for(e=e.a;v(e.b.a,e.a);e=e.c.b);for(;v(e.a,e.b.a);e=e.e);for(i=e.c.b,r=void 0;e.e!==i;)if(v(e.b.a,i.a)){for(;i.e!==e&&(v((a=i.e).b.a,a.a)||E(i.a,i.b.a,i.e.b.a)<=0);)i=(r=y(i.e,i)).b;i=i.c.b}else{for(;i.e!==e&&(v((t=e.c.b).a,t.b.a)||0<=E(e.b.a,e.a,e.c.b.a));)e=(r=y(e,e.c.b)).b;e=e.e}for(;i.e.e!==e;)r=y(i.e,i),i=r.b}if(this.h||this.i||this.k||this.l)if(this.m){for(n=(s=this.b).a.b;n!==s.a;n=n.b)if(n.c){for(this.h&&this.h(2,this.c),e=n.a;this.k&&this.k(e.a.d,this.c),(e=e.e)!==n.a;);this.i&&this.i(this.c)}}else{for(s=this.b,n=!!this.l,e=!1,i=-1,r=s.a.d;r!==s.a;r=r.d)if(r.c)for(e||(this.h&&this.h(4,this.c),e=!0),h=r.a;n&&(o=h.b.d.c?0:1,i!==o)&&(i=o,this.l)&&this.l(!!i,this.c),this.k&&this.k(h.a.d,this.c),(h=h.e)!==r.a;);e&&this.i&&this.i(this.c)}if(this.r){for(s=this.b,e=s.a.b;e!==s.a;e=n)if(n=e.b,!e.c){for(r=(i=e.a).e,h=void 0;r=(h=r).e,(h.d=null)===h.b.d&&(h.c===h?m(h.a,null):(h.a.c=h.c,N(h,h.b.e)),(o=h.b).c===o?m(o.a,null):(o.a.c=o.c,N(o,o.b.e)),F(h)),h!==i;);i=e.d,((e=e.b).d=i).b=e}return this.r(this.b),void(this.c=this.b=null)}}this.b=this.c=null},this.libtess={GluTesselator:a,windingRule:{GLU_TESS_WINDING_ODD:100130,GLU_TESS_WINDING_NONZERO:100131,GLU_TESS_WINDING_POSITIVE:100132,GLU_TESS_WINDING_NEGATIVE:100133,GLU_TESS_WINDING_ABS_GEQ_TWO:100134},primitiveType:{GL_LINE_LOOP:2,GL_TRIANGLES:4,GL_TRIANGLE_STRIP:5,GL_TRIANGLE_FAN:6},errorType:{GLU_TESS_MISSING_BEGIN_POLYGON:100151,GLU_TESS_MISSING_END_POLYGON:100153,GLU_TESS_MISSING_BEGIN_CONTOUR:100152,GLU_TESS_MISSING_END_CONTOUR:100154,GLU_TESS_COORD_TOO_LARGE:100155,GLU_TESS_NEED_COMBINE_CALLBACK:100156},gluEnum:{GLU_TESS_MESH:100112,GLU_TESS_TOLERANCE:100142,GLU_TESS_WINDING_RULE:100140,GLU_TESS_BOUNDARY_ONLY:100141,GLU_INVALID_ENUM:100900,GLU_INVALID_VALUE:100901,GLU_TESS_BEGIN:100100,GLU_TESS_VERTEX:100101,GLU_TESS_END:100102,GLU_TESS_ERROR:100103,GLU_TESS_EDGE_FLAG:100104,GLU_TESS_COMBINE:100105,GLU_TESS_BEGIN_DATA:100106,GLU_TESS_VERTEX_DATA:100107,GLU_TESS_END_DATA:100108,GLU_TESS_ERROR_DATA:100109,GLU_TESS_EDGE_FLAG_DATA:100110,GLU_TESS_COMBINE_DATA:100111}},a.prototype.gluDeleteTess=a.prototype.x,a.prototype.gluTessProperty=a.prototype.B,a.prototype.gluGetTessProperty=a.prototype.y,a.prototype.gluTessNormal=a.prototype.A,a.prototype.gluTessCallback=a.prototype.z,a.prototype.gluTessVertex=a.prototype.C,a.prototype.gluTessBeginPolygon=a.prototype.u,a.prototype.gluTessBeginContour=a.prototype.t,a.prototype.gluTessEndContour=a.prototype.v,a.prototype.gluTessEndPolygon=a.prototype.w,void 0!==i&&(i.exports=this.libtess)},{}],2:[function(t,a,e){var r=new(t("./tessellator"));function n(){this.list=[],this.hash=Object.create(null)}function p(t,a){t=s(t),a=s(a);t!==a&&(t.rankE?(h[_].first=b,c[_].last=f,r=[d[0]-p[0],d[1]-p[1]],n=1/Math.sqrt(r[0]*r[0]+r[1]*r[1]),h[_].miter_first=c[_].miter_last=[p[0]+T*r[0]*n,p[1]+T*r[1]*n]):(h[_].first=d,c[_].last=d),2===g&&(h[_].remove_middle_first=!0,c[_].remove_middle_last=!0)):(s=[d[0]-t.ortho[0],d[1]-t.ortho[1]],u=[d[0]+a.ortho[0],d[1]+a.ortho[1]],G.push({done:[t.index,a.index],todo:[_,t.to,a.to],rectangles:[[b,d,s,p],[f,d,u,p]]}),h[_].first=b,c[_].last=f)})}),S.forEach(function(t){var a,e,i=t.edge,r=i[0],i=i[1],n=t.points[r],s=t.points[i],r=d[r].coords,i=d[i].coords,t=t.polygon=[];L(n.last,s.first,r,i)<0?(a=n.last_vertex,e=s.first_vertex,p(S[a],S[e]),t.push(o(n.miter_last||r,n.last,s.first,s.miter_first||i))):t.push(n.last,s.first),s.miter_first&&t.push(s.miter_first),s.remove_middle_first&&s.remove_middle_last||t.push(i),s.miter_last&&t.push(s.miter_last),L(n.first,s.last,r,i)<0?(a=n.first_vertex,e=s.last_vertex,p(S[a],S[e]),t.push(o(n.first,n.miter_first||r,s.miter_last||i,s.last))):t.push(s.last,n.first),n.miter_first&&t.push(n.miter_first),n.remove_middle_first&&n.remove_middle_last||t.push(r),n.miter_last&&t.push(n.miter_last)}),Object.create(null)),i=(G.forEach(function(t){for(var h,c=Object.create(null),a=t.done[0],e=t.done[1],b=S[a],i=S[e],u=(p(b,i),c[a]=!0,c[e]=!0,new n),f=(t.todo.forEach(function(t){u.push(t)}),t.rectangles[0]),l=t.rectangles[1];null!==(h=u.pop());)d[h].neighList.forEach(function(t){var a,e,i,r,n,s,o=t.index;!c[o]&&(a=t.to,(s=_[o])||(n=d[h].coords,r=d[a].coords,e=[n[0]+t.ortho[0],n[1]+t.ortho[1]],i=[r[0]+t.ortho[0],r[1]+t.ortho[1]],r=[r[0]-t.ortho[0],r[1]-t.ortho[1]],n=[n[0]-t.ortho[0],n[1]-t.ortho[1]],s=_[o]=[e,i,r,n]),c[o]=!0,v(s,f)||v(s,l))&&(p(b,S[o]),u.push(a))})}),[]);S.forEach(function(t,a){0