PageSourceSearch

https://www.speakupaustin.org/Scripts/commentMap.js

js speakupaustin.org collected 2026-10-01 13:53:13 UTC 13,132 bytes, 426 lines download raw bytes

1var COMMENTS_MAP_DEFAULT_WIDTH = 600;
2var COMMENTS_MAP_DEFAULT_ZOOM = 13;
3var COMMENTS_MAP_CENTER = { lat: 37, lng: -95 };
4
5// Comments Map Pin Scaling Configuration
6var COMMENTS_MAP_SCALING = {
7  baseSizes: {
8    xs: { width: 15, height: 15 },
9    sm: { width: 20, height: 20 },
10    md: { width: 25, height: 25 },
11    lg: { width: 30, height: 30 },
12    xl: { width: 35, height: 35 }
13  },
14  zoomScaling: {
15    minZoom: 3,
16    maxZoom: 18,
17    minScale: 0.5,
18    maxScale: 2.0,
19    defaultScale: 1.0
20  }
21};
22
23// Calculate zoom-responsive scale factor for comments map
24function calculateCommentsMapZoomScale(zoom) {
25  var config = COMMENTS_MAP_SCALING.zoomScaling;
26  zoom = Math.max(config.minZoom, Math.min(config.maxZoom, zoom));
27  var zoomRange = config.maxZoom - config.minZoom;
28  var zoomProgress = (zoom - config.minZoom) / zoomRange;
29  var scaleRange = config.maxScale - config.minScale;
30  return config.minScale + zoomProgress * scaleRange;
31}
32
33// Get scaled size for comments map pins
34function getCommentsMapScaledPinSize(sizeCategory, zoom) {
35  var baseSize;
36  if (typeof sizeCategory === "string") {
37    baseSize =
38      COMMENTS_MAP_SCALING.baseSizes[sizeCategory] ||
39      COMMENTS_MAP_SCALING.baseSizes.sm;
40  } else if (sizeCategory && sizeCategory.width && sizeCategory.height) {
41    baseSize = sizeCategory;
42  } else {
43    baseSize = COMMENTS_MAP_SCALING.baseSizes.sm; // Default to small for comments
44  }
45
46  var scaleFactor = calculateCommentsMapZoomScale(zoom);
47  return {
48    width: Math.round(baseSize.width * scaleFactor),
49    height: Math.round(baseSize.height * scaleFactor)
50  };
51}
52
53function CommentsMap(config) {
54  var self = this;
55  self.pollComments = config.pollComments || [];
56  self.$container = config.$container;
57  self.mapWidth = config.mapWidth || COMMENTS_MAP_DEFAULT_WIDTH;
58  self.zoom = config.zoom || COMMENTS_MAP_DEFAULT_ZOOM;
59  self.kmlFiles = config.kmlFiles || [];
60
61  //This is the Cityzen Map Item associated with this poll. See Map.cs
62  self.chart = config.chart || {};
63  self.insight = config.insight || config.chart.insight || {};
64
65  self.$mapContainer = self.$container.find(".map-container");
66  self.$container.find(".ModeSwitch").remove();
67  self.$mapContainer =
68    self.$mapContainer ||
69    $("<div></div>")
70      .css("maxWidth", self.mapWidth)
71      .css("margin", "10px auto")
72      .height(self._getMapHeight())
73      .insertBefore($("center", self.$container));
74
75  self.marks = [];
76  $.each(self.pollComments, function (ind, commentData) {
77    if (commentData.parentId === 0) {
78      self.marks.push({
79        infoWindowHtml: self.createInfoWindowHtml(commentData),
80        commentText: commentData.commentText,
81        lat: commentData.lat,
82        lng: commentData.long,
83        linePoints: commentData.linePoints
84          ? JSON.parse(commentData.linePoints)
85          : [],
86        pinImageId: commentData.pinImageId
87      });
88    }
89  });
90
91  var defaultBasemap = "roadmap";
92  if (self.chart && self.chart.getMapOptions) {
93    defaultBasemap = self.chart.getMapOptions().defaultBaseMap;
94  }
95
96  // AB#9979: name the map region before Maps takes the element over (see map-a11y.js).
97  window.piMapA11y &&
98    window.piMapA11y.describe(
99      self.$mapContainer,
100      "Comment location map",
101      self.marks.length
102        ? "Map plotting the location of " +
103            self.marks.length +
104            (self.marks.length === 1 ? " comment." : " comments.")
105        : "Map of comment locations. No located comments to display."
106    );
107
108  self.map = new google.maps.Map(self.$mapContainer[0], {
109    zoom: self.zoom,
110    center: COMMENTS_MAP_CENTER,
111    zoomControl: true,
112    mapTypeId: defaultBasemap,
113    gestureHandling: "cooperative",
114    mapTypeControl: false,
115    clickableIcons: false,
116    streetViewControl: false,
117    fullscreenControl: true
118  });
119
120  $.each(self.kmlFiles, function (ind, kmlFile) {
121    var kmlLayer = new google.maps.KmlLayer({
122      url: kmlFile.serverFullPathAndFileName,
123      map: self.map,
124      preserveViewport: true,
125      zIndex: 1
126    });
127  });
128
129  var bounds = new google.maps.LatLngBounds();
130
131  self.infowindow = null;
132  self.markers = [];
133  self.polylines = [];
134  $.each(self.marks, function (ind, mark) {
135    if (mark.linePoints.length) {
136      if ($.trim(mark.commentText) !== "") {
137        var polyline = new google.maps.Polyline({
138          path: mark.linePoints,
139          strokeOpacity: 1.0,
140          strokeColor: "#0000BF",
141          strokeWeight: 3,
142          map: self.map,
143          zIndex: 2
144        });
145
146        polyline.addListener(
147          "click",
148          function () {
149            if (this.infowindow) {
150              this.infowindow.close();
151            }
152
153            this.infowindow = new google.maps.InfoWindow({
154              content: mark.infoWindowHtml,
155              position: { lat: mark.lat, lng: mark.lng }
156            });
157
158            this.infowindow.open(this.map, polyline);
159          }.bind(self)
160        );
161
162        self.polylines.push(polyline);
163
164        $.each(mark.linePoints, function (ind, point) {
165          bounds.extend(point);
166        });
167      } else {
168        var polyline = new google.maps.Polyline({
169          path: mark.linePoints,
170          strokeOpacity: 0.5,
171          strokeColor: "#0000BF",
172          strokeWeight: 3,
173          map: self.map,
174          zIndex: 2
175        });
176
177        self.polylines.push(polyline);
178
179        $.each(mark.linePoints, function (ind, point) {
180          bounds.extend(point);
181        });
182      }
183    } else {
184      const pinIcon = {
185        path: "M348.748,106.141C348.748,47.532,301.212,0,242.604,0c-58.609,0-106.139,47.532-106.139,106.141 c0, 53.424, 39.61, 97.196, 90.976, 104.598v274.473h30.327V210.739C309.119, 203.337, 348.748, 159.565, 348.748, 106.141z",
186        fillColor: "#0000ff",
187        fillOpacity: 1,
188        scale: 0.05,
189        strokeColor: "#0000ff",
190        strokeOpacity: 1,
191        strokeWeight: 1
192      };
193
194      if (mark.lat == 0 || mark.lng == 0) return;
195
196      if ($.trim(mark.commentText) !== "") {
197        var currentZoom = self.map.getZoom() || COMMENTS_MAP_DEFAULT_ZOOM;
198        var markerIcon;
199
200        if (mark.pinImageId == "-1") {
201          markerIcon = pinIcon;
202        } else {
203          // Calculate scaled size for custom pin
204          var scaledSize = getCommentsMapScaledPinSize(
205            { width: 20, height: 20 },
206            currentZoom
207          );
208          markerIcon = {
209            url: "/img/" + mark.pinImageId + "_20_20",
210            scaledSize: new google.maps.Size(
211              scaledSize.width,
212              scaledSize.height
213            )
214          };
215        }
216
217        const marker = new google.maps.Marker({
218          position: mark,
219          map: self.map,
220          icon: markerIcon,
221          zIndex: 3
222        });
223
224        // Store original pin info for scaling
225        if (mark.pinImageId != "-1") {
226          marker._originalPinSize = { width: 20, height: 20 };
227          marker._pinImageUrl = "/img/" + mark.pinImageId + "_20_20";
228        }
229
230        marker.addListener(
231          "click",
232          function () {
233            if (this.infowindow) {
234              this.infowindow.close();
235            }
236
237            this.infowindow = new google.maps.InfoWindow({
238              content: mark.infoWindowHtml
239            });
240
241            this.infowindow.open(this.map, marker);
242          }.bind(self)
243        );
244
245        self.markers.push(marker);
246        bounds.extend(marker.getPosition());
247      } else {
248        pinIcon.fillColor = "#ffffff";
249
250        var currentZoom = self.map.getZoom() || COMMENTS_MAP_DEFAULT_ZOOM;
251        var markerIcon;
252
253        if (mark.pinImageId == "-1") {
254          markerIcon = pinIcon;
255        } else {
256          // Calculate scaled size for custom pin
257          var scaledSize = getCommentsMapScaledPinSize(
258            { width: 20, height: 20 },
259            currentZoom
260          );
261          markerIcon = {
262            url: "/img/" + mark.pinImageId + "_20_20",
263            scaledSize: new google.maps.Size(
264              scaledSize.width,
265              scaledSize.height
266            )
267          };
268        }
269
270        const marker = new google.maps.Marker({
271          position: mark,
272          map: self.map,
273          icon: markerIcon,
274          zIndex: 3
275        });
276
277        // Store original pin info for scaling
278        if (mark.pinImageId != "-1") {
279          marker._originalPinSize = { width: 20, height: 20 };
280          marker._pinImageUrl = "/img/" + mark.pinImageId + "_20_20";
281        }
282
283        self.markers.push(marker);
284        bounds.extend(marker.getPosition());
285      }
286    }
287  });
288  self.chart.map = self.map;
289  self.chart.lookupPresetMapBoundsOrApplyDefaults(bounds);
290  if (self.needsZoomInit) self.applyZoomToMapImages();
291  self.chart.addDefaultMapListeners();
292  self.chart.addMapLayerManager();
293
294  // Add zoom change listener for pin scaling
295  self.map.addListener("zoom_changed", function () {
296    self.updatePinSizes();
297  });
298}
299
300// Update pin sizes based on current zoom level
301CommentsMap.prototype.updatePinSizes = function () {
302  var self = this;
303  var currentZoom = this.map.getZoom();
304
305  // Update all markers with stored pin size information
306  if (this.markers && Array.isArray(this.markers)) {
307    this.markers.forEach(function (marker) {
308      if (marker._originalPinSize && marker._pinImageUrl) {
309        var scaledSize = getCommentsMapScaledPinSize(
310          marker._originalPinSize,
311          currentZoom
312        );
313
314        marker.setIcon({
315          url: marker._pinImageUrl,
316          scaledSize: new google.maps.Size(scaledSize.width, scaledSize.height)
317        });
318      }
319    });
320  }
321};
322
323CommentsMap.prototype.addShadedBoundary = function () {
324  // _redrawBoundaries in mappoll.js
325  var self = this;
326  var allWorld = [
327    new google.maps.LatLng(85, 180),
328    new google.maps.LatLng(85, 90),
329    new google.maps.LatLng(85, 0),
330    new google.maps.LatLng(85, -90),
331    new google.maps.LatLng(85, -180),
332    new google.maps.LatLng(0, -180),
333    new google.maps.LatLng(-85, -180),
334    new google.maps.LatLng(-85, -90),
335    new google.maps.LatLng(-85, 0),
336    new google.maps.LatLng(-85, 90),
337    new google.maps.LatLng(-85, 180),
338    new google.maps.LatLng(0, 180),
339    new google.maps.LatLng(85, 180)
340  ];
341
342  self.map.data.setStyle({
343    //cursor: "not-allowed",
344    fillColor: "#000000",
345    fillOpacity: 0.25,
346    strokeWeight: 0
347  });
348  if (self.boundaryArray.length > 0) {
349    self._boundariesPolygon = new google.maps.Polygon({
350      paths: self.boundaryArray
351    });
352
353    self.map.data.add({
354      geometry: new google.maps.Data.Polygon([allWorld, self.boundaryArray])
355    });
356  } else {
357    this._boundariesPolygon = null;
358  }
359};
360
361// map must fit to screen height
362CommentsMap.prototype._getMapHeight = function () {
363  return Math.min(screen.height - 50, 500);
364};
365
366CommentsMap.prototype.createInfoWindowHtml = function (commentData) {
367  var self = this;
368  var result = "";
369  if (
370    commentData.attachmentsJSON != null &&
371    commentData.attachmentsJSON.length
372  ) {
373    var attachmentInfo = JSON.parse(commentData.attachmentsJSON);
374    if (attachmentInfo.Id && attachmentInfo.Id.length) {
375      result +=
376        '<div class="comment-info-img"><img src="/img/' +
377        attachmentInfo.Id +
378        "_300_300\" data-action='zoom' data-zoom-src=\"/img/" +
379        attachmentInfo.Id +
380        '"></div>';
381      self.needsZoomInit = true;
382    }
383  }
384  result +=
385    '<div class="comment-info-text">' + commentData.commentText + "</div>";
386  return result;
387};
388
389CommentsMap.prototype.applyZoomToMapImages = function () {
390  var self = this;
391  ScriptLoader.loadScriptArray(
392    ScriptLoader.scriptArrays.magnificPopup,
393    function () {
394      window.TOOLS &&
395        TOOLS.requireCSSFile("/lib/magnific-popup.js/magnific-popup.min.css");
396      self.$container.find("[data-action='zoom']").each(function () {
397        var $img = $(this);
398        if ($img.parent().is("a")) return;
399        var zoomSrc =
400          $img.data("original") || $img.data("zoom-src") || $img.attr("src");
401        var $a = $("<a>")
402          .attr("href", zoomSrc)
403          .attr("title", $img.attr("title") || $img.attr("alt"))
404          .attr(
405            "aria-label",
406            "Zoom image " + ($img.attr("title") || $img.attr("alt"))
407          )
408          .addClass("image-zoom-popup");
409        $img.wrap($a);
410      });
411      self.$container.find(".image-zoom-popup").magnificPopup({
412        closeOnContentClick: false,
413        type: "image",
414        closeBtnInside: true,
415        autoFocusLast: true,
416        image: {
417          verticalFit: true
418        },
419        zoom: {
420          enabled: true,
421          duration: 300 // don't foget to change the duration also in CSS
422        }
423      });
424    }
425  );
426};

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.