PageSourceSearch

https://bikeindex.org/assets/controllers/registrations/show/map_co…f2b66633779fc7198e68579e3d110be5465f920e.js

js bikeindex.org collected 2026-10-01 10:24:46 UTC 2,685 bytes, 89 lines download raw bytes

1import { Controller } from '@hotwired/stimulus'
2import { ExpandControl, groundRadiusStops, loadMapLibre, MAPS_STYLE_URL, OSM_ATTRIBUTION, showMapUnavailable } from 'utils/maplibre'
3
4/* global IntersectionObserver */
5
6// Connects to data-controller='registrations--show--map'
7// Renders a map centered on the coordinates, marking them with a pin (point) or
8// a translucent red circle (approximate area).
9
10// A translucent circle covering the approximate area
11const CIRCLE_PAINT = (radiusMeters, latitude) => ({
12  'circle-radius': groundRadiusStops(radiusMeters, latitude),
13  'circle-color': 'red',
14  'circle-opacity': 0.4
15})
16
17export default class extends Controller {
18  static targets = ['canvas', 'unavailable', 'pin']
19  static values = {
20    latitude: Number,
21    longitude: Number,
22    radiusMeters: Number
23  }
24
25  // Load only once on screen — it can sit in a collapsed panel or below the fold
26  connect () {
27    this.observer = new IntersectionObserver((entries) => {
28      if (!entries.some((entry) => entry.isIntersecting)) return
29      this.observer.disconnect()
30      this.#load()
31    }, { rootMargin: '200px' })
32    this.observer.observe(this.element)
33  }
34
35  async #load () {
36    try {
37      const maplibregl = await loadMapLibre()
38      if (!this.element.isConnected) return // disconnected while loading
39
40      this.#render(maplibregl)
41    } catch (error) {
42      showMapUnavailable(error, {
43        source: this.identifier,
44        map: this.map,
45        canvas: this.canvasTarget,
46        message: this.hasUnavailableTarget ? this.unavailableTarget : null
47      })
48      this.map = null
49    }
50  }
51
52  disconnect () {
53    this.observer?.disconnect()
54    this.map?.remove()
55    this.map = null
56  }
57
58  #render (maplibregl) {
59    const center = [this.longitudeValue, this.latitudeValue]
60    this.map = new maplibregl.Map({
61      container: this.canvasTarget,
62      style: MAPS_STYLE_URL,
63      center,
64      zoom: 13,
65      maxZoom: 16,
66      attributionControl: { customAttribution: OSM_ATTRIBUTION }
67    })
68    this.map.addControl(new ExpandControl(), 'top-right')
69
70    if (this.hasPinTarget) {
71      const element = this.pinTarget.content.firstElementChild.cloneNode(true)
72      new maplibregl.Marker({ element, anchor: 'bottom' }).setLngLat(center).addTo(this.map)
73      return
74    }
75
76    this.map.on('load', () => {
77      this.map.addSource('location', {
78        type: 'geojson',
79        data: { type: 'Feature', geometry: { type: 'Point', coordinates: center } }
80      })
81      this.map.addLayer({
82        id: 'location',
83        type: 'circle',
84        source: 'location',
85        paint: CIRCLE_PAINT(this.radiusMetersValue, this.latitudeValue)
86      })
87    })
88  }
89};

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.