PageSourceSearch

https://www.boston.gov/modules/custom/bos_content/modules/node_project/js/node_project_map_utils.js?v=20260629

js boston.gov collected 2026-09-24 07:23:56 UTC 19,548 bytes, 508 lines download raw bytes

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.