├── .jshintrc
├── README.md
├── css
└── style.css
├── index.html
└── js
├── map.js
├── polylabel.min.js
└── turf_intersect.min.js
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "esversion": 6
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # dynamic_labels
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | #map {
7 | position: absolute;
8 | top: 0;
9 | bottom: 0;
10 | width: 100%;
11 | }
12 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dynamic Neighborhood Label
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/js/map.js:
--------------------------------------------------------------------------------
1 | mapboxgl.accessToken = 'pk.eyJ1Ijoiam9yZGFubWFwIiwiYSI6IjRUOVBuV28ifQ.ubu4SCJhADfVRbncXCXiPg';
2 |
3 | // initial basemap
4 | var map = new mapboxgl.Map({
5 | container: 'map',
6 | style: 'mapbox://styles/jordanmap/cjfndkntp1v4w2spazxnvdbxm',
7 | center: [-73.9576740104957, 40.7274924718489],
8 | zoom: 14
9 | });
10 |
11 | // set true to turn on tile boundaires for debugging
12 | // map.showTileBoundaries = true;
13 |
14 | var nbhdCentroid = {};
15 | nbhdCentroid.type = "FeatureCollection";
16 | nbhdCentroid.features = [];
17 |
18 | map.on('load', function() {
19 |
20 | map.addSource("nbhdSourceData", {
21 | type: "vector",
22 | url: "mapbox://jordanmap.6h12tv9x"
23 | });
24 |
25 | // add neighborhood layer
26 | map.addLayer({
27 | "id": "nbhd",
28 | "type": "fill",
29 | "source": "nbhdSourceData",
30 | "source-layer": "nbhd_20180327-dgzidp",
31 | "paint": {
32 | "fill-color": "transparent"
33 | }
34 | });
35 |
36 | map.addLayer({
37 | "id": "nbhd_outline",
38 | "type": "line",
39 | "source": "nbhdSourceData",
40 | "source-layer": "nbhd_20180327-dgzidp",
41 | "paint": {
42 | "line-color": "#000",
43 | "line-width": 3
44 | }
45 | });
46 |
47 | map.addLayer({
48 | "id": "nbhd_label",
49 | "type": "symbol",
50 | "source": {
51 | type: "vector",
52 | url: "mapbox://jordanmap.byu32psd"
53 | },
54 | "source-layer": "nbhd_label_20180327-2e9xr0",
55 | "layout": {
56 | 'text-field': '{small_neighborhood}',
57 | 'text-font': ["Lato Bold"],
58 | 'text-size': {
59 | "base": 1,
60 | "stops": [
61 | [12, 12],
62 | [16, 16]
63 | ]
64 | },
65 | "text-padding": 3,
66 | "text-letter-spacing": 0.1,
67 | "text-max-width": 7,
68 | "text-transform": "uppercase"
69 | },
70 | "paint": {
71 | "text-color": "#333",
72 | "text-halo-color": "hsl(0, 0%, 100%)",
73 | "text-halo-width": 1.5,
74 | "text-halo-blur": 1
75 | }
76 | });
77 |
78 | map.addSource('nbhdCentroid', {
79 | type: 'geojson',
80 | data: nbhdCentroid
81 | });
82 |
83 | map.addLayer({
84 | "id": "nbhd_centroids",
85 | "type": "symbol",
86 | "source": "nbhdCentroid",
87 | "layout": {
88 | 'text-field': '{small_neighborhood}',
89 | 'text-font': ["Lato Bold"],
90 | 'text-size': {
91 | "base": 1,
92 | "stops": [
93 | [12, 12],
94 | [16, 16]
95 | ]
96 | },
97 | "text-padding": 3,
98 | "text-letter-spacing": 0.1,
99 | "text-max-width": 7,
100 | "text-transform": "uppercase",
101 | "text-allow-overlap": true
102 | },
103 | "paint": {
104 | "text-color": "#333",
105 | "text-halo-color": "hsl(0, 0%, 100%)",
106 | "text-halo-width": 1.5,
107 | "text-halo-blur": 1
108 | }
109 | });
110 |
111 | map.on('moveend', function(e) {
112 | var tileLoad = setInterval(function() {
113 | if (map.loaded()) {
114 | dyLabels(map);
115 | clearInterval(tileLoad);
116 | }
117 | }, 300);
118 | });
119 | });
120 |
121 | function dyLabels(map) {
122 | nbhdCentroid.features = [];
123 | var nbhdFeatures = map.queryRenderedFeatures({
124 | layers: ["nbhd"]
125 | });
126 |
127 | var mapSW = map.getBounds()._sw;
128 | var mapNE = map.getBounds()._ne;
129 |
130 | var mapViewBound = {
131 | type: "Feature",
132 | geometry: {
133 | type: "Polygon",
134 | coordinates: [
135 | [
136 | [mapSW.lng, mapSW.lat],
137 | [mapSW.lng, mapNE.lat],
138 | [mapNE.lng, mapNE.lat],
139 | [mapNE.lng, mapSW.lat],
140 | [mapSW.lng, mapSW.lat]
141 | ]
142 | ]
143 | }
144 | };
145 |
146 | var visualCenterList = [];
147 |
148 | var fixedLabelFilter = ["!in", "small_neighborhood"];
149 |
150 | var neighborhoods = groupBy(nbhdFeatures, nbhdFeature => nbhdFeature.properties.small_neighborhood);
151 | neighborhoods.forEach(function(value, key) {
152 | var lngOfCentroid = JSON.parse(value[0].properties.centroid).coordinates[0];
153 | var latOfCentroid = JSON.parse(value[0].properties.centroid).coordinates[1];
154 | if (lngOfCentroid <= mapSW.lng || lngOfCentroid >= mapNE.lng || latOfCentroid <= mapSW.lat || latOfCentroid >= mapNE.lat) {
155 | fixedLabelFilter.push(key);
156 | // console.log(key);
157 | // console.log(key,value);
158 | var visualCenter = value.map(obj => getVisualCenter(obj, mapViewBound));
159 | if (visualCenter.clean().length) {
160 | visualCenterList.push(visualCenter.clean());
161 | }
162 | }
163 | });
164 | visualCenterList.map(obj => {
165 | var coordinatesList = [];
166 | obj.forEach(function(feature){
167 | coordinatesList.push(feature.geometry.coordinates);
168 | });
169 | var center = getCenter(coordinatesList);
170 | var neighborhoodCenterFeature = {
171 | type: "Feature",
172 | geometry: {
173 | type: "Point",
174 | coordinates: center
175 | },
176 | properties: {
177 | small_neighborhood: obj[0].properties.small_neighborhood,
178 | minlng: obj[0].properties.minlng,
179 | minlat: obj[0].properties.minlat,
180 | maxlng: obj[0].properties.maxlng,
181 | maxlat: obj[0].propertiesmaxlat
182 | }
183 | };
184 | nbhdCentroid.features.push(neighborhoodCenterFeature);
185 | });
186 | map.setFilter("nbhd_label", fixedLabelFilter);
187 | map.getSource('nbhdCentroid').setData(nbhdCentroid);
188 | }
189 | //
190 | // groupBy function
191 | function groupBy(list, keyGetter) {
192 | var map = new Map();
193 | list.forEach(function(item) {
194 | var key = keyGetter(item);
195 | var collection = map.get(key);
196 | if (!collection) {
197 | map.set(key, [item]);
198 | } else {
199 | collection.push(item);
200 | }
201 | });
202 | return map;
203 | }
204 |
205 | // get visual center
206 | function getVisualCenter(feature, mapViewBound) {
207 | if (feature.geometry.type == "Polygon") {
208 | var intersection = turf.intersect(mapViewBound, feature.geometry);
209 | if (intersection) {
210 | var visualCenter = {
211 | type: "Feature",
212 | geometry: {
213 | type: "Point",
214 | coordinates: []
215 | },
216 | properties: {}
217 | };
218 | if(intersection.geometry.coordinates.length > 1) {
219 | var intersections = [];
220 | intersection.geometry.coordinates.forEach(function(coordinate){
221 | intersections.push(polylabel(coordinate));
222 | });
223 | visualCenter.geometry.coordinates = getCenter(intersections);
224 | } else {
225 | visualCenter.geometry.coordinates = polylabel(intersection.geometry.coordinates);
226 | }
227 | visualCenter.properties.small_neighborhood = feature.properties.small_neighborhood;
228 | visualCenter.properties.minlng = feature.properties.minlng;
229 | visualCenter.properties.minlat = feature.properties.minlat;
230 | visualCenter.properties.maxlng = feature.properties.maxlng;
231 | visualCenter.properties.maxlat = feature.properties.maxlat;
232 | return visualCenter;
233 | }
234 | }
235 | }
236 |
237 | // get the center of a coordinates list
238 | function getCenter(coordinates) {
239 | var lngList = [];
240 | var latList = [];
241 | coordinates.map(coordinate => {
242 | lngList.push(coordinate[0]);
243 | latList.push(coordinate[1]);
244 | });
245 | var meanLng = lngList.reduce((p,c) => p + c, 0) / lngList.length;
246 | var meanLat = latList.reduce((p,c) => p + c, 0) / latList.length;
247 | return [meanLng, meanLat];
248 | }
249 |
250 | // remove undefined from an array
251 | Array.prototype.clean = function() {
252 | for (var i = 0; i < this.length; i++) {
253 | if (!this[i]) {
254 | this.splice(i, 1);
255 | i--;
256 | }
257 | }
258 | return this;
259 | };
260 |
--------------------------------------------------------------------------------
/js/polylabel.min.js:
--------------------------------------------------------------------------------
1 | function TinyQueue(t,e){if(!(this instanceof TinyQueue))return new TinyQueue(t,e);if(this.data=t||[],this.length=this.data.length,this.compare=e||defaultCompare,this.length>0)for(var n=(this.length>>1)-1;n>=0;n--)this._down(n)}function defaultCompare(t,e){return te?1:0}function polylabel(t,e,n){var i,r,a,h;e=e||1;for(var o=0;oa)&&(a=s[0]),(!o||s[1]>h)&&(h=s[1])}var l=a-i,u=h-r,f=Math.min(l,u),d=f/2,p=new TinyQueue(null,compareMax);if(0===f)return[i,r];for(var g=i;gv.d&&(v=y);for(var C=p.length;p.length;){var m=p.pop();m.d>v.d&&(v=m,n&&console.log("found best %d after %d probes",Math.round(1e4*m.d)/1e4,C)),m.max-v.d<=e||(d=m.h/2,p.push(new Cell(m.x-d,m.y-d,d,t)),p.push(new Cell(m.x+d,m.y-d,d,t)),p.push(new Cell(m.x-d,m.y+d,d,t)),p.push(new Cell(m.x+d,m.y+d,d,t)),C+=4)}return n&&(console.log("num probes: "+C),console.log("best distance: "+v.d)),[v.x,v.y]}function compareMax(t,e){return e.max-t.max}function Cell(t,e,n,i){this.x=t,this.y=e,this.h=n,this.d=pointToPolygonDist(t,e,i),this.max=this.d+this.h*Math.SQRT2}function pointToPolygonDist(t,e,n){for(var i=!1,r=1/0,a=0;ae!=f[1]>e&&t<(f[0]-u[0])*(e-u[1])/(f[1]-u[1])+u[0]&&(i=!i),r=Math.min(r,getSegDistSq(t,e,u,f))}return(i?1:-1)*Math.sqrt(r)}function getCentroidCell(t){for(var e=0,n=0,i=0,r=t[0],a=0,h=r.length,o=h-1;a1?(r=i[0],a=i[1]):s>0&&(r+=h*s,a+=o*s)}return(h=t-r)*h+(o=e-a)*o}TinyQueue.prototype={push:function(t){this.data.push(t),this.length++,this._up(this.length-1)},pop:function(){if(0!==this.length){var t=this.data[0];return this.length--,this.length>0&&(this.data[0]=this.data[this.length],this._down(0)),this.data.pop(),t}},peek:function(){return this.data[0]},_up:function(t){for(var e=this.data,n=this.compare,i=e[t];t>0;){var r=t-1>>1,a=e[r];if(n(i,a)>=0)break;e[t]=a,t=r}e[t]=i},_down:function(t){for(var e=this.data,n=this.compare,i=this.length>>1,r=e[t];t=0)break;e[t]=o,t=a}e[t]=r}};
2 |
--------------------------------------------------------------------------------