├── .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 | --------------------------------------------------------------------------------