1/** 2 * @file 3 * Map utilities for node_project. See comment at bottom for example use. 4 * 5 * @see https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLngBounds 6 * @see https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLng 7 */ 8(function ($, Drupal) { 9 Drupal.behaviors.cobMapUtilities = { 10 attach: function (context, settings) { 11 12 /** 13 * Extends the GoogleMaps bounds class. 14 * @see https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLngBounds 15 * @see https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLng 16 */ 17 window.cobBounds = function () { 18 this.count = 0 19 this.sumLat = 0; 20 this.sumLng = 0; 21 /** @var google.maps.LatLngBounds bounds */ 22 this.bounds = new google.maps.LatLngBounds(); 23 } 24 cobBounds.prototype = { 25 addLatLng: function (lat, lng) { 26 this.bounds.extend(new google.maps.LatLng(lat, lng)); 27 this.sumLat += lat; 28 this.sumLng += lng; 29 this.count++; 30 }, 31 getCenter: function () { 32 const center = this.bounds.getCenter(); 33 return { 34 lat: center.lat(), 35 lng: center.lng() 36 } 37 }, 38 getAvgCenter: function () { 39 return { 40 lat: this.sumLat / this.count, 41 lng: this.sumLng / this.count, 42 } 43 }, 44 countLatLng: function () { 45 return this.count; 46 }, 47 getBounds: function () { 48 return this.bounds; 49 } 50 }; 51 52 /** 53 * Initializes and sets up a predefined geolocation configuration within 54 * the `drupalSettings` object for City of Boston (COB) mapping utilities. 55 * This method ensures that the `cob.geolocation.gis` configuration is 56 * available with specific settings related to Boston's geographic center, 57 * city hall location, bounding box, and vertices. 58 * 59 * It is then extended to provide utilities which manipulate the map on 60 * the node_project pages. 61 * 62 * @return {void} This function does not return a value as it modifies the 63 * `drupalSettings` object in place. 64 */ 65 window.CobMapUtilities = function () { 66 drupalSettings.cob = drupalSettings.cob || {}; 67 drupalSettings.cob.geolocation = drupalSettings.cob.geolocation || {}; 68 drupalSettings.cob.geolocation = { gis: {}, markers: [], shapes: [] }; 69 drupalSettings.cob.geolocation.gis = { 70 boston: { 71 center: [-71.0550099338379, 42.35205457086276], 72 cityhall: [-71.0582715, 42.3603], 73 box: { 74 north: 42.397, 75 east: -70.922, 76 south: 42.227, 77 west: -71.192, 78 }, 79 vertices: [ 80 [-71.191283, 42.396907], 81 [-70.922590, 42.396907], 82 [-70.922590, 42.227965], 83 [-71.191283, 42.227965], 84 ], 85 }, 86 }; 87 88 } 89 CobMapUtilities.prototype = { 90 91 geopoly: { 92 type: "GeometryCollection", 93 geometries:[{ 94 type: "", 95 coordinates: [[]] 96 }], 97 }, 98 99 /** 100 * Updates the map by resetting specific data attributes and reattaching 101 * behaviors. 102 * 103 * This method modifies a specific element identified by the class 104 * `geolocation-geometry-widget-google-maps-geojson` by altering its 105 * `data-once` attribute to remove a particular processed state. 106 * Following the attribute update, it reattaches the Drupal behavior 107 * for the geolocation geometry widget related to Google Maps. 108 * 109 * @return {void} Does not return a value. 110 */ 111 updateMap: function () { 112 const wrapper = $(".geolocation-geometry-widget-google-maps-geojson"); 113 wrapper.attr("data-once", wrapper.attr("data-once").replace("geolocation-geometry-processed", "")); 114 Drupal.behaviors["geolocationGeometryWidgetGoogleMaps"].attach(document); 115 }, 116 117 /** 118 * checks that the GeoJSOn object supplied is in the correct format. 119 * If the GeoJSON data does not have a "geometries" property, it wraps 120 * the data into a GeoJSON GeometryCollection wrapper. 121 * 122 * @param {object} json 123 * The input element containing the GeoJSON data as an object. 124 * @return {object} 125 * No value is returned. 126 */ 127 checkGeoJson: function (json) { 128 if (typeof json.geometries === "undefined") { 129 json = { 130 type: "GeometryCollection", 131 geometries: [ 132 json, 133 ] 134 } 135 } 136 return json; 137 }, 138 139 /** 140 * Ensures that GeoJson in the supplied element is in the expected 141 * format. 142 * 143 * @param {HTMLElement} element 144 * The input element containing the GeoJSON data as a string value. 145 * @return {void} 146 * No value is returned. 147 */ 148 updateGeoJson: function (element) { 149 try { 150 let json = JSON.parse($(element).val()); 151 json = this.checkGeoJson(json); 152 $(element).val(JSON.stringify(json)); 153 } 154 catch (e) { 155 $(element).val(JSON.stringify({})); 156 } 157 }, 158 159 /** 160 * Adds a marker to a specified map and records it in the configuration 161 * settings. 162 * 163 * @param {google.maps.Marker} marker 164 * The marker to be added to the map. 165 * @param {GeolocationGoogleMap} map - The map instance to which the 166 * marker should be added. 167 * @return {void} This function does not return a value. 168 */ 169 addMarker: function (marker, map) {
170 marker.setMap(map); 171 drupalSettings.cob.geolocation.markers.push(marker); 172 google.maps.event.addListener(marker, 'dragstart', () => { 173 const pos = marker.getPosition(); 174 let parsedGeoJson = JSON.parse($("#edit-field-project-gis-pts-0-geojson").val()); 175 parsedGeoJson = checkGeoJson(parsedGeoJson); 176 let newGeoJson = { type: "GeometryCollection", geometries: [] }; 177 $.each(parsedGeoJson.geometries, function (index, point) { 178 if (!(point.coordinates[0] === pos.lng() && point.coordinates[1] === pos.lat())) { 179 newGeoJson.push(parsedGeoJson[index]); 180 } 181 }); 182 $("#edit-field-project-gis-pts-0-geojson").val(JSON.stringify(newGeoJson)); 183 }); 184 google.maps.event.addListener(marker, 'dragend', () => { 185 const pos = marker.getPosition(); 186 let parsedGeoJson = JSON.parse($("#edit-field-project-gis-pts-0-geojson").val()); 187 parsedGeoJson = checkGeoJson(parsedGeoJson); 188 let newGeoJson = { type: "GeometryCollection", geometries: [] }; 189 parsedGeoJson.geometries.push({ 190 type: 'Point', 191 coordinates: [pos.lng(), pos.lat()], 192 }); 193 $("#edit-field-project-gis-pts-0-geojson").val(JSON.stringify(parsedGeoJson)); 194 }); 195 }, 196 removeMarkers: function () { 197 $.each(drupalSettings.cob.geolocation.markers, function (index, marker) { 198 google.maps.event.clearInstanceListeners(marker); 199 marker.setMap(null); 200 }); 201 }, 202 203 /** 204 * Adds a shape to the map and registers it within the application's 205 * geolocation settings. 206 * 207 * @param {google.maps.Polygon} shape 208 * The shape object to be added. 209 * @param {GeolocationGoogleMap} map 210 * The map object where the shape will be displayed. 211 * @return {void} 212 */ 213 addShape: function (shape, map) { 214 shape.setMap(map); 215 drupalSettings.cob.geolocation.shapes.push(shape); 216 // TODO verify listeners work. 217 google.maps.event.addListener(shape, 'dragstart', () => { 218 const pos = shape.getPosition(); 219 console.log(pos.lat()); 220 }); 221 }, 222 removeShapes: function () { 223 $.each(drupalSettings.cob.geolocation.shapes, function (index, shape) { 224 google.maps.event.clearInstanceListeners(shape); 225 shape.setMap(null); 226 }); 227 }, 228 229 /** 230 * Sets the zoom level for a specified map. 231 * 232 * @function setZoom 233 * @param {string} id 234 * The unique identifier of the map. 235 * @param {number} zoom 236 * The desired zoom level to apply to the map. 237 */ 238 setZoom: function (id, zoom) { 239 let map = Drupal.geolocation.getMapById(id); 240 map.setZoom(zoom); 241 // map.googleMap.setZoom(zoom); 242 }, 243 244 /** 245 * Sets the center of a specified map to the given latitude and longitude. 246 * 247 * @param {string} id 248 * The ID of the map to center. 249 * @param {number} lat 250 * The latitude value for the new center. 251 * @param {number} long 252 * The longitude value for the new center. 253 */ 254 setCenter: function (id, lat, long) { 255 let map = Drupal.geolocation.getMapById(id); 256 map.setCenterByCoordinates(new google.maps.LatLng(lat, long)); 257 // map.googleMap.setCenterByCoordinates(new google.maps.LatLng(lat, long)); 258 }, 259 260 /** 261 * Adjusts the zoom level of a map to include all the specified shapes, 262 * such as points or polygons. 263 * 264 * @param {string} id 265 * The identifier of the map. 266 * @param {Array} shapes 267 * An array of shape objects to be included in the bounds. Each shape 268 * can be of type "point" or "polygon". 269 * 270 * Uses Google's LatLngBounds to calculate and adjust the bounds for the 271 * shapes provided. The map is then fitted to the calculated bounds with 272 * a specified padding. 273 */ 274 setZoomToShape: function (id, shapes) { 275 let map = Drupal.geolocation.getMapById(id); 276 const bounds = new cobBounds(); 277 const boundsFromShape = function (arrShapes) { 278 $.each(arrShapes, function (index, shapeType) { 279 if (shapeType.type === "GeometryCollection") { 280 boundsFromShape(shapeType.geometries); 281 } 282 else if (shapeType.type === "Point") {
283 bounds.addLatLng(shapeType.coordinates[1],shapeType.coordinates[0]); 284 } 285 else if (shapeType.type === "Polygon" || shapeType.type === "PolyLine") { 286 $.each(shapeType.coordinates, function (index, shapeGrp) { 287 $.each(shapeGrp, function (index, shape) { 288 bounds.addLatLng(shape[1], shape[0]); 289 }); 290 }); 291 } 292 }); 293 } 294// map.googleMap.fitBounds(bounds, { padding: { top: 20, bottom: 20, left: 25, right: 25 } }); 295 boundsFromShape(shapes); 296 map.googleMap.fitBounds(bounds.getBounds()); 297 map.setCenterByCoordinates(new google.maps.LatLng(bounds.getCenter().lat, bounds.getCenter().lng)) 298 }, 299 300 /** 301 * Finds the Map data from the page and and returns a {this.geopoly} 302 * object (which is just a GeoJSON object). 303 * 304 * @param {string} map_id 305 * The identifier of the map. 306 * @param {HTMLElement} element 307 * The input element which containing the GeoJSON data. 308 * When on an entity_form_display (edit-form), this should be a 309 * "geolocation-geometry-widget-google-maps-geojson" element, and when 310 * on a entity_view_display (webpage) this should be a 311 * ".geolocation-map-wrapper" element. 312 */ 313 getShape: function (map_id, element) { 314 315 let map = Drupal.geolocation.getMapById(map_id); 316 let util = new CobMapUtilities(); 317 let data = $(element).find("span:has(meta)"); 318 319 if (data.length) { 320 321 let shape = this.geopoly; 322 323 switch (data.attr('typeof')) { 324 case 'GeoCoordinates': 325 // Drupal.geolocation.getMapById(map_id).googleMap.maxZoom = 17; 326 shape.geometries[0].type = "Point"; 327 shape.geometries[0].coordinates = [ 328 data.find("[property=longitude]").attr("content"), 329 data.find("[property=latitude]").attr("content") 330 ]; 331 break; 332 333 case 'GeoShape': 334 let marker = []; 335 shape.geometries[0].type = "Polygon"; 336 shape.geometries[0].coordinates = [[]]; 337 $.each($(element).find("meta"), function (index, meta) { 338 meta = $(meta); 339 const content = meta.attr("content"); 340 if (meta.attr("property") === "polygon" || meta.attr("property") === "line") { 341 $.each(content.split(' '), function (index, coord) { 342 if (coord !== '') { 343 const latlong = coord.split(','); 344 shape.geometries[0].coordinates[0].push([ 345 latlong[1], latlong[0], 346 ]); 347 } 348 }); 349 } 350 else { 351 if (meta.attr("property") === "latitude") { 352 marker[1] = parseFloat(content); 353 } 354 else if (meta.attr("property") === "longitude") { 355 marker[0] = parseFloat(content); 356 } 357 if (typeof marker[0] !== "undefined" && typeof marker[1] !== "undefined") { 358 shape.geometries[0].coordinates[0].push(marker); 359 marker = []; 360 } 361 } 362 }); 363 break; 364 } 365 return shape; 366 } 367 else { 368 data = $(element).find(".geolocation-geometry-widget-geojson-input"); 369 if (data.length && data.val()) { 370 return JSON.parse(data.val()); 371 } 372 else { 373 return false; 374 } 375 } 376 }, 377 378 /** 379 * Adjusts and sets the bounds of the Google Map instance to fit all 380 * provided vertices associated with the Boston area. 381 * It calculates the bounding box based on the given geolocation data 382 * and centers the map accordingly. 383 * 384 * @param {Object} map 385 * The map object which includes the Google Map instance and utility 386 * methods. 387 */ 388 setBostonBounds: function (map) { 389 const bounds = new cobBounds(); 390 $.each(drupalSettings.cob.geolocation.gis.boston.vertices, function (idx, latlng) { 391 bounds.addLatLng(latlng[1], latlng[0]); 392 }); 393 map.googleMap.fitBounds(bounds.getBounds()); 394 let cityhall = drupalSettings.cob.geolocation.gis.boston.cityhall; 395 map.setCenterByCoordinates(new google.maps.LatLng(cityhall[1], cityhall[0])) 396 }, 397 398 } 399 400 } 401 } 402})(jQuery, Drupal); 403/* 404 Example Usage: 405*/ 406/* 407 Update the GeoJson Input field when it is changed: 408 let util = new CobMapUtilities(); 409 const text = $(".geolocation-geometry-widget-geojson-input"); 410 text.on("change", function (event) { 411 util.updateGeoJson(this); 412 } 413*/ 414/* 415 Add a new point on the map (element is the textarea with GeoJson): 416 let util = new CobMapUtilities(); 417 let midpoint = new cobBounds(); 418 util.updateGeoJson(element); 419 let geoJson = JSON.parse($(element).val()); 420 421 // [OPTIONAL] - to remove existing markers. 422 let id = $("#edit-field-project-gis-poly-0 .geolocation-map-wrapper").attr("id"); // Alter if there are multiple maps. 423 let map = Drupal.geolocation.getMapById(id); 424 map.googleMap.data.setMap(null); // Removes all markers and shapes already on map. 425 util.removeMarkers(); // Removes markers we added on map. 426 427 $.each(geoJson.geometries, function (index, pt) { 428 let point = { 429 position: new google.maps.LatLng(pt.coordinates[1],pt.coordinates[0]), 430 draggable: true, 431 visible: true, 432 } 433 let marker = new google.maps.Marker(point); 434 util.addMarker(marker, map.googleMap); 435 midpoint.addLatLong(pt.coordinates[1], pt.coordinates[0]) 436 }); 437*/ 438/* 439 Add a new shape on the map (element is the textarea with GeoJson): 440 let util = new CobMapUtilities(); 441 let midpoint = new cobBounds(); 442 util.updateGeoJson(element); 443 let geoJson = JSON.parse($(element).val()); 444 445 // [OPTIONAL] - to remove existing markers. 446 let id = $("#edit-field-project-gis-poly-0 .geolocation-map-wrapper").attr("id"); // Alter if there are multiple maps. 447 let map = Drupal.geolocation.getMapById(id); 448 map.googleMap.data.setMap(null); // Removes all markers and shapes already on map. 449 util.removeShapes(); // Removes shapes we added on map. 450 451 $.each(geoJson.geometries, function (index, shape) { 452 let path = []; 453 $.each(shape.coordinates, function (index, set) { 454 $.each(set, function (index, shape) { 455 path.push(new google.maps.LatLng(shape[1], shape[0])); 456 midpoint.addLatLong(shape[1], shape[0]); 457 }); 458 }); 459 let poly = new google.maps.Polygon({ 460 shape: shape.type.toLowerCase(), 461 paths: path, 462 fillColor: "#ff0000", 463 fillOpacity: 0.15, 464 editable: true, 465 draggable: true 466 }); 467 util.addShape(poly, map.googleMap); 468 }); 469*/ 470/* 471 Center map on a point: 472 let util = new CobMapUtilities(); 473 let id = $("#edit-field-project-gis-poly-0 .geolocation-map-wrapper").attr("id"); // Alter if there are multiple maps. 474 util.setCenter(id, lat, long); 475*/ 476/* 477 Center map on center of a shape (element is the textarea GeoJSON): 478 let util = new CobMapUtilities(); 479 let midpoint = new cobBounds(); 480 util.updateGeoJson(element); 481 let geoJson = JSON.parse($(element).val()); 482 $.each(geoJson.geometries, function (index, shape) { 483 $.each(shape.coordinates, function (index, set) { 484 $.each(set, function (index, shape) { 485 midpoint.addLatLong(shape[1], shape[0]); 486 }); 487 }); 488 }); 489 let id = $("#edit-field-project-gis-poly-0 .geolocation-map-wrapper").attr("id"); // Alter if there are multiple maps. 490 util.setCenter(id, midpoint.midPoint().lat, midpoint.midPoint().long) 491*/ 492/* 493 Zoom map to a zoom level: 494 let util = new CobMapUtilities(); 495 let id = $("#edit-field-project-gis-poly-0 .geolocation-map-wrapper").attr("id"); // Alter if there are multiple maps. 496 let map = Drupal.geolocation.getMapById(id); 497 const zoom = map.googleMap.getZoom(); // get existing zoom level directly. 498 .. mess around with map ... 499 util.setZoom(id, zoom) 500*/ 501/* 502 Zoom map to fit a GeoJSON shape: 503 let util = new CobMapUtilities(); 504 util.updateGeoJson(element); 505 let geoJson = JSON.parse($(element).val()); 506 let id = $("#edit-field-project-gis-poly-0 .geolocation-map-wrapper").attr("id"); // Alter if there are multiple maps. 507 util.setZoomToShape(id, geoJson.geometries) 508*/
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.