Proof of concept for the other one
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

idk

+11495 -116
+4
.gitignore
··· 1 + node_modules/ 2 + main.js 3 + *.js.map 4 + .DS_Store
+305 -34
calendarView.ts
··· 1 - import { ItemView, WorkspaceLeaf, debounce, TFile } from "obsidian"; 1 + import { ItemView, MarkdownView, WorkspaceLeaf, debounce } from "obsidian"; 2 + import type CalendarViewerPlugin from "./main"; 2 3 import { parseEvents, CalendarEvent } from "./parser"; 3 - import { renderCalendar } from "./renderer"; 4 + import { renderCalendar, CalendarController } from "./renderer"; 5 + import { createMap, MapController } from "./mapRenderer"; 6 + import { geocodeEvents } from "./geocoder"; 4 7 5 8 export const VIEW_TYPE_CALENDAR = "calendar-viewer"; 6 9 7 10 export class CalendarView extends ItemView { 11 + private plugin: CalendarViewerPlugin; 8 12 private currentMonth: Date; 9 13 private events: CalendarEvent[] = []; 14 + private selectedEvent: CalendarEvent | null = null; 15 + private hasUserNavigated = false; 16 + 17 + private calendarEl: HTMLElement | null = null; 18 + private mapEl: HTMLElement | null = null; 19 + private calendarController: CalendarController | null = null; 20 + private mapController: MapController | null = null; 21 + 22 + private resizeObserver: ResizeObserver | null = null; 23 + /** Guard: true while we're writing geo lines back to the doc */ 24 + private isWritingGeoLines = false; 25 + private eventsFingerprint: string = ""; 26 + private cursorPollInterval: ReturnType<typeof setInterval> | null = null; 27 + private lastCursorLine: number = -1; 10 28 11 - constructor(leaf: WorkspaceLeaf) { 29 + constructor(leaf: WorkspaceLeaf, plugin: CalendarViewerPlugin) { 12 30 super(leaf); 13 - // Default to current month 31 + this.plugin = plugin; 14 32 this.currentMonth = new Date(); 15 33 this.currentMonth.setDate(1); 16 34 } ··· 28 46 } 29 47 30 48 async onOpen(): Promise<void> { 49 + const content = this.containerEl.children[1] as HTMLElement; 50 + content.empty(); 51 + content.addClass("cal-view-root"); 52 + 53 + this.calendarEl = content.createDiv({ cls: "cal-pane-top" }); 54 + this.mapEl = content.createDiv({ cls: "cal-pane-bottom" }); 55 + 56 + // Watch for resize to keep the Leaflet map valid 57 + this.resizeObserver = new ResizeObserver(() => { 58 + this.mapController?.invalidateSize(); 59 + }); 60 + this.resizeObserver.observe(this.mapEl); 61 + 62 + // Poll editor cursor position to sync selection 63 + this.cursorPollInterval = setInterval(() => { 64 + this.pollCursorPosition(); 65 + }, 200); 66 + 31 67 await this.refresh(); 32 68 } 33 69 34 70 async onClose(): Promise<void> { 35 - // cleanup handled by plugin 71 + if (this.cursorPollInterval !== null) { 72 + clearInterval(this.cursorPollInterval); 73 + this.cursorPollInterval = null; 74 + } 75 + this.mapController?.destroy(); 76 + this.mapController = null; 77 + this.resizeObserver?.disconnect(); 78 + this.resizeObserver = null; 36 79 } 37 80 38 81 /** 39 - * Re-parse the active note and re-render the calendar. 82 + * Re-parse the active note, geocode events, and re-render everything. 40 83 */ 41 84 refresh = debounce(async () => { 85 + // Skip re-parse while we're writing geo lines back to the document 86 + if (this.isWritingGeoLines) return; 87 + 42 88 const file = this.app.workspace.getActiveFile(); 43 89 if (file) { 44 90 const content = await this.app.vault.read(file); 45 91 this.events = parseEvents(content); 46 92 47 - // If we have events, jump to the month of the nearest upcoming event 48 93 if (this.events.length > 0 && !this.hasUserNavigated) { 49 94 this.jumpToNearestMonth(); 50 95 } ··· 52 97 this.events = []; 53 98 } 54 99 55 - this.render(); 100 + const newFingerprint = this.computeFingerprint(this.events); 101 + const eventsChanged = newFingerprint !== this.eventsFingerprint; 102 + this.eventsFingerprint = newFingerprint; 103 + 104 + this.selectedEvent = null; 105 + this.renderCalendar(); 106 + 107 + if (eventsChanged) { 108 + this.initOrUpdateMap(); 109 + // Geocode in background — progressively updates the map 110 + await this.geocodeAndUpdateMap(); 111 + } 56 112 }, 300, true); 57 113 58 - private hasUserNavigated = false; 114 + /** 115 + * Reset the user-navigated flag when switching notes. 116 + */ 117 + resetNavigation(): void { 118 + this.hasUserNavigated = false; 119 + } 120 + 121 + // ── Private ── 122 + 123 + /** 124 + * Compute a simple fingerprint of the event list (titles + dates). 125 + * Used to detect whether events actually changed between refreshes. 126 + */ 127 + private computeFingerprint(events: CalendarEvent[]): string { 128 + return events 129 + .map((e) => `${e.title}::${e.date.getTime()}::${e.venue ?? ""}::${e.location ?? ""}`) 130 + .join("|"); 131 + } 59 132 60 133 private jumpToNearestMonth(): void { 61 134 const now = new Date(); 62 - // Find the nearest upcoming event (or the latest if all are past) 63 135 const sorted = [...this.events].sort( 64 136 (a, b) => a.date.getTime() - b.date.getTime() 65 137 ); ··· 70 142 } 71 143 } 72 144 73 - private render(): void { 74 - const container = this.containerEl.children[1] as HTMLElement; 75 - renderCalendar(container, this.currentMonth, this.events, { 76 - onPrevMonth: () => { 77 - this.hasUserNavigated = true; 78 - this.currentMonth = new Date( 79 - this.currentMonth.getFullYear(), 80 - this.currentMonth.getMonth() - 1, 81 - 1 82 - ); 83 - this.render(); 145 + private renderCalendar(): void { 146 + if (!this.calendarEl) return; 147 + 148 + this.calendarController = renderCalendar( 149 + this.calendarEl, 150 + this.currentMonth, 151 + this.events, 152 + { 153 + onPrevMonth: () => { 154 + this.hasUserNavigated = true; 155 + this.currentMonth = new Date( 156 + this.currentMonth.getFullYear(), 157 + this.currentMonth.getMonth() - 1, 158 + 1, 159 + ); 160 + this.renderCalendar(); 161 + }, 162 + onNextMonth: () => { 163 + this.hasUserNavigated = true; 164 + this.currentMonth = new Date( 165 + this.currentMonth.getFullYear(), 166 + this.currentMonth.getMonth() + 1, 167 + 1, 168 + ); 169 + this.renderCalendar(); 170 + }, 171 + onEventClick: (event: CalendarEvent) => { 172 + this.selectedEvent = event; 173 + // Tell the map to select this event 174 + this.mapController?.selectEvent(event); 175 + // Scroll the editor to this event's line 176 + this.scrollEditorToEvent(event); 177 + }, 178 + }, 179 + this.selectedEvent, 180 + ); 181 + } 182 + 183 + private initOrUpdateMap(): void { 184 + if (!this.mapEl) return; 185 + 186 + if (this.mapController) { 187 + // Map already exists — just update markers 188 + this.mapController.updateMarkers(this.events); 189 + } else { 190 + // Create map for the first time 191 + this.mapController = createMap(this.mapEl, this.events, { 192 + onMarkerClick: (event: CalendarEvent) => { 193 + this.selectedEvent = event; 194 + 195 + // Navigate calendar to the event's month and highlight it 196 + const evMonth = event.date.getMonth(); 197 + const evYear = event.date.getFullYear(); 198 + if ( 199 + evYear !== this.currentMonth.getFullYear() || 200 + evMonth !== this.currentMonth.getMonth() 201 + ) { 202 + this.hasUserNavigated = true; 203 + this.currentMonth = new Date(evYear, evMonth, 1); 204 + } 205 + this.renderCalendar(); 206 + 207 + // Scroll the editor to this event's line 208 + this.scrollEditorToEvent(event); 209 + }, 210 + }); 211 + } 212 + } 213 + 214 + private async geocodeAndUpdateMap(): Promise<void> { 215 + if (this.events.length === 0) return; 216 + 217 + const newlyGeocoded = await geocodeEvents( 218 + this.events, 219 + // onProgress: update the map with new markers but don't re-fit bounds 220 + () => { 221 + this.mapController?.updateMarkers(this.events, false); 84 222 }, 85 - onNextMonth: () => { 86 - this.hasUserNavigated = true; 87 - this.currentMonth = new Date( 88 - this.currentMonth.getFullYear(), 89 - this.currentMonth.getMonth() + 1, 90 - 1 91 - ); 92 - this.render(); 223 + ); 224 + 225 + // Write geo: lines back into the document for newly geocoded events 226 + if (newlyGeocoded.length > 0) { 227 + await this.writeGeoLinesToDoc(newlyGeocoded); 228 + } 229 + } 230 + 231 + /** 232 + * Poll the active editor's cursor position and sync selection 233 + * to the event whose block contains the cursor line. 234 + */ 235 + private pollCursorPosition(): void { 236 + const mdView = this.app.workspace.getActiveViewOfType(MarkdownView); 237 + if (!mdView) return; 238 + 239 + const cursor = mdView.editor.getCursor(); 240 + const line = cursor.line; 241 + if (line === this.lastCursorLine) return; 242 + this.lastCursorLine = line; 243 + 244 + this.selectEventByLine(line); 245 + } 246 + 247 + /** 248 + * Find the event whose startLine..endLine range contains the given 249 + * line number and select it in the calendar and map. 250 + */ 251 + private selectEventByLine(line: number): void { 252 + const event = this.events.find( 253 + (e) => 254 + e.startLine !== undefined && 255 + e.endLine !== undefined && 256 + line >= e.startLine && 257 + line <= e.endLine, 258 + ); 259 + 260 + if (!event) { 261 + // Cursor is outside any event block — clear selection 262 + if (this.selectedEvent) { 263 + this.selectedEvent = null; 264 + this.calendarController?.selectEvent(null); 265 + this.mapController?.selectEvent(null); 266 + } 267 + return; 268 + } 269 + 270 + // Already selected 271 + if ( 272 + this.selectedEvent && 273 + this.selectedEvent.title === event.title && 274 + this.selectedEvent.date.getTime() === event.date.getTime() 275 + ) { 276 + return; 277 + } 278 + 279 + this.selectedEvent = event; 280 + 281 + // Navigate calendar to the event's month if needed, then highlight 282 + const evMonth = event.date.getMonth(); 283 + const evYear = event.date.getFullYear(); 284 + if ( 285 + evYear !== this.currentMonth.getFullYear() || 286 + evMonth !== this.currentMonth.getMonth() 287 + ) { 288 + this.currentMonth = new Date(evYear, evMonth, 1); 289 + this.renderCalendar(); 290 + } else { 291 + this.calendarController?.selectEvent(event); 292 + } 293 + 294 + this.mapController?.selectEvent(event); 295 + } 296 + 297 + /** 298 + * Scroll the editor to the event's start line. 299 + */ 300 + private scrollEditorToEvent(event: CalendarEvent): void { 301 + if (event.startLine === undefined) return; 302 + const mdView = this.app.workspace.getActiveViewOfType(MarkdownView); 303 + if (!mdView) return; 304 + 305 + const editor = mdView.editor; 306 + editor.setCursor({ line: event.startLine, ch: 0 }); 307 + editor.scrollIntoView( 308 + { 309 + from: { line: event.startLine, ch: 0 }, 310 + to: { line: event.startLine, ch: 0 }, 93 311 }, 94 - }); 312 + true, 313 + ); 314 + 315 + // Update tracked cursor so we don't re-trigger polling 316 + this.lastCursorLine = event.startLine; 95 317 } 96 318 97 319 /** 98 - * Reset the user-navigated flag so the view jumps to the 99 - * relevant month when switching to a new note. 320 + * Write "geo: lat,lng" sub-bullets back into the active document 321 + * for events that were just geocoded. Inserts after each event's 322 + * last line (endLine), processing from bottom to top so line 323 + * numbers don't shift for earlier insertions. 100 324 */ 101 - resetNavigation(): void { 102 - this.hasUserNavigated = false; 325 + private async writeGeoLinesToDoc(newlyGeocoded: CalendarEvent[]): Promise<void> { 326 + const file = this.app.workspace.getActiveFile(); 327 + if (!file) return; 328 + 329 + // Filter to events that have valid line info and coords 330 + const toWrite = newlyGeocoded.filter( 331 + (e) => 332 + e.endLine !== undefined && 333 + e.lat !== undefined && 334 + e.lng !== undefined, 335 + ); 336 + if (toWrite.length === 0) return; 337 + 338 + // Sort by endLine descending so insertions don't shift earlier lines 339 + toWrite.sort((a, b) => b.endLine! - a.endLine!); 340 + 341 + this.isWritingGeoLines = true; 342 + try { 343 + await this.app.vault.process(file, (content) => { 344 + const lines = content.split("\n"); 345 + 346 + for (const event of toWrite) { 347 + const insertAfter = event.endLine!; 348 + const geoLine = `\t* geo: ${event.lat},${event.lng}`; 349 + 350 + // Check if a geo: line already exists in this block 351 + // (in case of a race between geocoding and re-parse) 352 + let alreadyHasGeo = false; 353 + for (let i = event.startLine ?? insertAfter; i <= insertAfter; i++) { 354 + if (/^\t[*\-]\s+geo:\s/.test(lines[i]) || /^\s{2,}[*\-]\s+geo:\s/.test(lines[i])) { 355 + alreadyHasGeo = true; 356 + break; 357 + } 358 + } 359 + if (alreadyHasGeo) continue; 360 + 361 + // Insert the geo line after endLine 362 + lines.splice(insertAfter + 1, 0, geoLine); 363 + } 364 + 365 + return lines.join("\n"); 366 + }); 367 + } finally { 368 + // Small delay before clearing the guard so the modify event 369 + // from vault.process has time to fire and be ignored 370 + setTimeout(() => { 371 + this.isWritingGeoLines = false; 372 + }, 500); 373 + } 103 374 } 104 375 }
+148
geocoder.ts
··· 1 + import type { CalendarEvent } from "./parser"; 2 + 3 + export interface GeocodedLocation { 4 + lat: number; 5 + lng: number; 6 + } 7 + 8 + const NOMINATIM_URL = "https://nominatim.openstreetmap.org/search"; 9 + const RATE_LIMIT_MS = 1100; // Nominatim requires max 1 req/sec 10 + 11 + /** 12 + * Build a geocoding query string from an event's venue and location. 13 + * Returns null if there's nothing to geocode. 14 + */ 15 + function buildQuery(event: CalendarEvent): string | null { 16 + const parts: string[] = []; 17 + if (event.venue) parts.push(event.venue); 18 + if (event.location) parts.push(event.location); 19 + if (parts.length === 0) return null; 20 + return parts.join(", "); 21 + } 22 + 23 + /** 24 + * Normalize a query string for deduplication. 25 + */ 26 + function normalizeQuery(query: string): string { 27 + return query.toLowerCase().trim(); 28 + } 29 + 30 + /** 31 + * Fetch a single geocode result from Nominatim. 32 + */ 33 + async function fetchGeocode(query: string): Promise<GeocodedLocation | null> { 34 + try { 35 + console.debug(`[CalendarViewer] Geocoding: "${query}"`); 36 + const params = new URLSearchParams({ 37 + q: query, 38 + format: "json", 39 + limit: "1", 40 + }); 41 + const response = await fetch(`${NOMINATIM_URL}?${params}`, { 42 + headers: { 43 + "User-Agent": "ObsidianCalendarViewer/1.0", 44 + }, 45 + }); 46 + if (!response.ok) { 47 + console.warn(`[CalendarViewer] Geocoding failed for "${query}": HTTP ${response.status}`); 48 + return null; 49 + } 50 + const results = await response.json(); 51 + if (results.length > 0) { 52 + const loc = { 53 + lat: parseFloat(results[0].lat), 54 + lng: parseFloat(results[0].lon), 55 + }; 56 + console.debug(`[CalendarViewer] Geocoded "${query}" → ${loc.lat}, ${loc.lng}`); 57 + return loc; 58 + } 59 + console.warn(`[CalendarViewer] Geocoding returned no results for "${query}"`); 60 + return null; 61 + } catch (e) { 62 + console.warn(`[CalendarViewer] Geocoding error for "${query}":`, e); 63 + return null; 64 + } 65 + } 66 + 67 + /** 68 + * Sleep for a given number of milliseconds. 69 + */ 70 + function sleep(ms: number): Promise<void> { 71 + return new Promise((resolve) => setTimeout(resolve, ms)); 72 + } 73 + 74 + /** 75 + * Geocode a list of events. Events that already have lat/lng (e.g. 76 + * from a "geo:" line in the document) are skipped. Uncached venues 77 + * are geocoded sequentially with rate limiting. 78 + * 79 + * The onProgress callback is called after each newly geocoded event, 80 + * so the map can progressively populate. 81 + * 82 + * Returns the list of events that were newly geocoded (so the caller 83 + * can write "geo:" lines back into the document). 84 + */ 85 + export async function geocodeEvents( 86 + events: CalendarEvent[], 87 + onProgress: () => void, 88 + ): Promise<CalendarEvent[]> { 89 + const needsGeocoding: Array<{ event: CalendarEvent; query: string; key: string }> = []; 90 + 91 + for (const event of events) { 92 + // Already has coordinates (from "geo:" line in doc) 93 + if (event.lat !== undefined && event.lng !== undefined) { 94 + console.debug(`[CalendarViewer] Skipping "${event.title}" — already has coords`); 95 + continue; 96 + } 97 + 98 + const query = buildQuery(event); 99 + if (!query) { 100 + console.debug(`[CalendarViewer] Skipping "${event.title}" — no venue/location`); 101 + continue; 102 + } 103 + 104 + needsGeocoding.push({ event, query, key: normalizeQuery(query) }); 105 + } 106 + 107 + // Deduplicate by normalized query (multiple events at same venue) 108 + const seen = new Set<string>(); 109 + const unique: typeof needsGeocoding = []; 110 + for (const item of needsGeocoding) { 111 + if (!seen.has(item.key)) { 112 + seen.add(item.key); 113 + unique.push(item); 114 + } 115 + } 116 + 117 + if (unique.length > 0) { 118 + console.debug(`[CalendarViewer] Geocoding ${unique.length} unique location(s)...`); 119 + } 120 + 121 + const newlyGeocoded: CalendarEvent[] = []; 122 + 123 + // Geocode sequentially with rate limiting 124 + for (let i = 0; i < unique.length; i++) { 125 + const { query, key } = unique[i]; 126 + 127 + if (i > 0) { 128 + await sleep(RATE_LIMIT_MS); 129 + } 130 + 131 + const result = await fetchGeocode(query); 132 + 133 + // Apply result to ALL events with the same normalized query 134 + if (result) { 135 + for (const item of needsGeocoding) { 136 + if (item.key === key) { 137 + item.event.lat = result.lat; 138 + item.event.lng = result.lng; 139 + newlyGeocoded.push(item.event); 140 + } 141 + } 142 + } 143 + 144 + onProgress(); 145 + } 146 + 147 + return newlyGeocoded; 148 + }
+10321 -47
main.js
··· 1 + var __create = Object.create; 1 2 var __defProp = Object.defineProperty; 2 3 var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 3 4 var __getOwnPropNames = Object.getOwnPropertyNames; 5 + var __getProtoOf = Object.getPrototypeOf; 4 6 var __hasOwnProp = Object.prototype.hasOwnProperty; 7 + var __commonJS = (cb, mod) => function __require() { 8 + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; 9 + }; 5 10 var __export = (target, all) => { 6 11 for (var name in all) 7 12 __defProp(target, name, { get: all[name], enumerable: true }); ··· 14 19 } 15 20 return to; 16 21 }; 22 + var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( 23 + // If the importer is in node compatibility mode or this is not an ESM 24 + // file that has been converted to a CommonJS file using a Babel- 25 + // compatible transform (i.e. "__esModule" has not been set), then set 26 + // "default" to the CommonJS "module.exports" for node compatibility. 27 + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, 28 + mod 29 + )); 17 30 var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 18 31 32 + // node_modules/leaflet/dist/leaflet-src.js 33 + var require_leaflet_src = __commonJS({ 34 + "node_modules/leaflet/dist/leaflet-src.js"(exports, module2) { 35 + (function(global, factory) { 36 + typeof exports === "object" && typeof module2 !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.leaflet = {})); 37 + })(exports, function(exports2) { 38 + "use strict"; 39 + var version = "1.9.4"; 40 + function extend(dest) { 41 + var i, j, len, src; 42 + for (j = 1, len = arguments.length; j < len; j++) { 43 + src = arguments[j]; 44 + for (i in src) { 45 + dest[i] = src[i]; 46 + } 47 + } 48 + return dest; 49 + } 50 + var create$2 = Object.create || /* @__PURE__ */ function() { 51 + function F() { 52 + } 53 + return function(proto) { 54 + F.prototype = proto; 55 + return new F(); 56 + }; 57 + }(); 58 + function bind(fn, obj) { 59 + var slice = Array.prototype.slice; 60 + if (fn.bind) { 61 + return fn.bind.apply(fn, slice.call(arguments, 1)); 62 + } 63 + var args = slice.call(arguments, 2); 64 + return function() { 65 + return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments); 66 + }; 67 + } 68 + var lastId = 0; 69 + function stamp(obj) { 70 + if (!("_leaflet_id" in obj)) { 71 + obj["_leaflet_id"] = ++lastId; 72 + } 73 + return obj._leaflet_id; 74 + } 75 + function throttle(fn, time, context) { 76 + var lock, args, wrapperFn, later; 77 + later = function() { 78 + lock = false; 79 + if (args) { 80 + wrapperFn.apply(context, args); 81 + args = false; 82 + } 83 + }; 84 + wrapperFn = function() { 85 + if (lock) { 86 + args = arguments; 87 + } else { 88 + fn.apply(context, arguments); 89 + setTimeout(later, time); 90 + lock = true; 91 + } 92 + }; 93 + return wrapperFn; 94 + } 95 + function wrapNum(x, range, includeMax) { 96 + var max = range[1], min = range[0], d = max - min; 97 + return x === max && includeMax ? x : ((x - min) % d + d) % d + min; 98 + } 99 + function falseFn() { 100 + return false; 101 + } 102 + function formatNum(num, precision) { 103 + if (precision === false) { 104 + return num; 105 + } 106 + var pow = Math.pow(10, precision === void 0 ? 6 : precision); 107 + return Math.round(num * pow) / pow; 108 + } 109 + function trim(str) { 110 + return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, ""); 111 + } 112 + function splitWords(str) { 113 + return trim(str).split(/\s+/); 114 + } 115 + function setOptions(obj, options) { 116 + if (!Object.prototype.hasOwnProperty.call(obj, "options")) { 117 + obj.options = obj.options ? create$2(obj.options) : {}; 118 + } 119 + for (var i in options) { 120 + obj.options[i] = options[i]; 121 + } 122 + return obj.options; 123 + } 124 + function getParamString(obj, existingUrl, uppercase) { 125 + var params = []; 126 + for (var i in obj) { 127 + params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + "=" + encodeURIComponent(obj[i])); 128 + } 129 + return (!existingUrl || existingUrl.indexOf("?") === -1 ? "?" : "&") + params.join("&"); 130 + } 131 + var templateRe = /\{ *([\w_ -]+) *\}/g; 132 + function template(str, data) { 133 + return str.replace(templateRe, function(str2, key) { 134 + var value = data[key]; 135 + if (value === void 0) { 136 + throw new Error("No value provided for variable " + str2); 137 + } else if (typeof value === "function") { 138 + value = value(data); 139 + } 140 + return value; 141 + }); 142 + } 143 + var isArray = Array.isArray || function(obj) { 144 + return Object.prototype.toString.call(obj) === "[object Array]"; 145 + }; 146 + function indexOf(array, el) { 147 + for (var i = 0; i < array.length; i++) { 148 + if (array[i] === el) { 149 + return i; 150 + } 151 + } 152 + return -1; 153 + } 154 + var emptyImageUrl = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="; 155 + function getPrefixed(name) { 156 + return window["webkit" + name] || window["moz" + name] || window["ms" + name]; 157 + } 158 + var lastTime = 0; 159 + function timeoutDefer(fn) { 160 + var time = +/* @__PURE__ */ new Date(), timeToCall = Math.max(0, 16 - (time - lastTime)); 161 + lastTime = time + timeToCall; 162 + return window.setTimeout(fn, timeToCall); 163 + } 164 + var requestFn = window.requestAnimationFrame || getPrefixed("RequestAnimationFrame") || timeoutDefer; 165 + var cancelFn = window.cancelAnimationFrame || getPrefixed("CancelAnimationFrame") || getPrefixed("CancelRequestAnimationFrame") || function(id) { 166 + window.clearTimeout(id); 167 + }; 168 + function requestAnimFrame(fn, context, immediate) { 169 + if (immediate && requestFn === timeoutDefer) { 170 + fn.call(context); 171 + } else { 172 + return requestFn.call(window, bind(fn, context)); 173 + } 174 + } 175 + function cancelAnimFrame(id) { 176 + if (id) { 177 + cancelFn.call(window, id); 178 + } 179 + } 180 + var Util = { 181 + __proto__: null, 182 + extend, 183 + create: create$2, 184 + bind, 185 + get lastId() { 186 + return lastId; 187 + }, 188 + stamp, 189 + throttle, 190 + wrapNum, 191 + falseFn, 192 + formatNum, 193 + trim, 194 + splitWords, 195 + setOptions, 196 + getParamString, 197 + template, 198 + isArray, 199 + indexOf, 200 + emptyImageUrl, 201 + requestFn, 202 + cancelFn, 203 + requestAnimFrame, 204 + cancelAnimFrame 205 + }; 206 + function Class() { 207 + } 208 + Class.extend = function(props) { 209 + var NewClass = function() { 210 + setOptions(this); 211 + if (this.initialize) { 212 + this.initialize.apply(this, arguments); 213 + } 214 + this.callInitHooks(); 215 + }; 216 + var parentProto = NewClass.__super__ = this.prototype; 217 + var proto = create$2(parentProto); 218 + proto.constructor = NewClass; 219 + NewClass.prototype = proto; 220 + for (var i in this) { 221 + if (Object.prototype.hasOwnProperty.call(this, i) && i !== "prototype" && i !== "__super__") { 222 + NewClass[i] = this[i]; 223 + } 224 + } 225 + if (props.statics) { 226 + extend(NewClass, props.statics); 227 + } 228 + if (props.includes) { 229 + checkDeprecatedMixinEvents(props.includes); 230 + extend.apply(null, [proto].concat(props.includes)); 231 + } 232 + extend(proto, props); 233 + delete proto.statics; 234 + delete proto.includes; 235 + if (proto.options) { 236 + proto.options = parentProto.options ? create$2(parentProto.options) : {}; 237 + extend(proto.options, props.options); 238 + } 239 + proto._initHooks = []; 240 + proto.callInitHooks = function() { 241 + if (this._initHooksCalled) { 242 + return; 243 + } 244 + if (parentProto.callInitHooks) { 245 + parentProto.callInitHooks.call(this); 246 + } 247 + this._initHooksCalled = true; 248 + for (var i2 = 0, len = proto._initHooks.length; i2 < len; i2++) { 249 + proto._initHooks[i2].call(this); 250 + } 251 + }; 252 + return NewClass; 253 + }; 254 + Class.include = function(props) { 255 + var parentOptions = this.prototype.options; 256 + extend(this.prototype, props); 257 + if (props.options) { 258 + this.prototype.options = parentOptions; 259 + this.mergeOptions(props.options); 260 + } 261 + return this; 262 + }; 263 + Class.mergeOptions = function(options) { 264 + extend(this.prototype.options, options); 265 + return this; 266 + }; 267 + Class.addInitHook = function(fn) { 268 + var args = Array.prototype.slice.call(arguments, 1); 269 + var init = typeof fn === "function" ? fn : function() { 270 + this[fn].apply(this, args); 271 + }; 272 + this.prototype._initHooks = this.prototype._initHooks || []; 273 + this.prototype._initHooks.push(init); 274 + return this; 275 + }; 276 + function checkDeprecatedMixinEvents(includes) { 277 + if (typeof L === "undefined" || !L || !L.Mixin) { 278 + return; 279 + } 280 + includes = isArray(includes) ? includes : [includes]; 281 + for (var i = 0; i < includes.length; i++) { 282 + if (includes[i] === L.Mixin.Events) { 283 + console.warn("Deprecated include of L.Mixin.Events: this property will be removed in future releases, please inherit from L.Evented instead.", new Error().stack); 284 + } 285 + } 286 + } 287 + var Events = { 288 + /* @method on(type: String, fn: Function, context?: Object): this 289 + * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`). 290 + * 291 + * @alternative 292 + * @method on(eventMap: Object): this 293 + * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}` 294 + */ 295 + on: function(types, fn, context) { 296 + if (typeof types === "object") { 297 + for (var type in types) { 298 + this._on(type, types[type], fn); 299 + } 300 + } else { 301 + types = splitWords(types); 302 + for (var i = 0, len = types.length; i < len; i++) { 303 + this._on(types[i], fn, context); 304 + } 305 + } 306 + return this; 307 + }, 308 + /* @method off(type: String, fn?: Function, context?: Object): this 309 + * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener. 310 + * 311 + * @alternative 312 + * @method off(eventMap: Object): this 313 + * Removes a set of type/listener pairs. 314 + * 315 + * @alternative 316 + * @method off: this 317 + * Removes all listeners to all events on the object. This includes implicitly attached events. 318 + */ 319 + off: function(types, fn, context) { 320 + if (!arguments.length) { 321 + delete this._events; 322 + } else if (typeof types === "object") { 323 + for (var type in types) { 324 + this._off(type, types[type], fn); 325 + } 326 + } else { 327 + types = splitWords(types); 328 + var removeAll = arguments.length === 1; 329 + for (var i = 0, len = types.length; i < len; i++) { 330 + if (removeAll) { 331 + this._off(types[i]); 332 + } else { 333 + this._off(types[i], fn, context); 334 + } 335 + } 336 + } 337 + return this; 338 + }, 339 + // attach listener (without syntactic sugar now) 340 + _on: function(type, fn, context, _once) { 341 + if (typeof fn !== "function") { 342 + console.warn("wrong listener type: " + typeof fn); 343 + return; 344 + } 345 + if (this._listens(type, fn, context) !== false) { 346 + return; 347 + } 348 + if (context === this) { 349 + context = void 0; 350 + } 351 + var newListener = { fn, ctx: context }; 352 + if (_once) { 353 + newListener.once = true; 354 + } 355 + this._events = this._events || {}; 356 + this._events[type] = this._events[type] || []; 357 + this._events[type].push(newListener); 358 + }, 359 + _off: function(type, fn, context) { 360 + var listeners, i, len; 361 + if (!this._events) { 362 + return; 363 + } 364 + listeners = this._events[type]; 365 + if (!listeners) { 366 + return; 367 + } 368 + if (arguments.length === 1) { 369 + if (this._firingCount) { 370 + for (i = 0, len = listeners.length; i < len; i++) { 371 + listeners[i].fn = falseFn; 372 + } 373 + } 374 + delete this._events[type]; 375 + return; 376 + } 377 + if (typeof fn !== "function") { 378 + console.warn("wrong listener type: " + typeof fn); 379 + return; 380 + } 381 + var index2 = this._listens(type, fn, context); 382 + if (index2 !== false) { 383 + var listener = listeners[index2]; 384 + if (this._firingCount) { 385 + listener.fn = falseFn; 386 + this._events[type] = listeners = listeners.slice(); 387 + } 388 + listeners.splice(index2, 1); 389 + } 390 + }, 391 + // @method fire(type: String, data?: Object, propagate?: Boolean): this 392 + // Fires an event of the specified type. You can optionally provide a data 393 + // object — the first argument of the listener function will contain its 394 + // properties. The event can optionally be propagated to event parents. 395 + fire: function(type, data, propagate) { 396 + if (!this.listens(type, propagate)) { 397 + return this; 398 + } 399 + var event = extend({}, data, { 400 + type, 401 + target: this, 402 + sourceTarget: data && data.sourceTarget || this 403 + }); 404 + if (this._events) { 405 + var listeners = this._events[type]; 406 + if (listeners) { 407 + this._firingCount = this._firingCount + 1 || 1; 408 + for (var i = 0, len = listeners.length; i < len; i++) { 409 + var l = listeners[i]; 410 + var fn = l.fn; 411 + if (l.once) { 412 + this.off(type, fn, l.ctx); 413 + } 414 + fn.call(l.ctx || this, event); 415 + } 416 + this._firingCount--; 417 + } 418 + } 419 + if (propagate) { 420 + this._propagateEvent(event); 421 + } 422 + return this; 423 + }, 424 + // @method listens(type: String, propagate?: Boolean): Boolean 425 + // @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean 426 + // Returns `true` if a particular event type has any listeners attached to it. 427 + // The verification can optionally be propagated, it will return `true` if parents have the listener attached to it. 428 + listens: function(type, fn, context, propagate) { 429 + if (typeof type !== "string") { 430 + console.warn('"string" type argument expected'); 431 + } 432 + var _fn = fn; 433 + if (typeof fn !== "function") { 434 + propagate = !!fn; 435 + _fn = void 0; 436 + context = void 0; 437 + } 438 + var listeners = this._events && this._events[type]; 439 + if (listeners && listeners.length) { 440 + if (this._listens(type, _fn, context) !== false) { 441 + return true; 442 + } 443 + } 444 + if (propagate) { 445 + for (var id in this._eventParents) { 446 + if (this._eventParents[id].listens(type, fn, context, propagate)) { 447 + return true; 448 + } 449 + } 450 + } 451 + return false; 452 + }, 453 + // returns the index (number) or false 454 + _listens: function(type, fn, context) { 455 + if (!this._events) { 456 + return false; 457 + } 458 + var listeners = this._events[type] || []; 459 + if (!fn) { 460 + return !!listeners.length; 461 + } 462 + if (context === this) { 463 + context = void 0; 464 + } 465 + for (var i = 0, len = listeners.length; i < len; i++) { 466 + if (listeners[i].fn === fn && listeners[i].ctx === context) { 467 + return i; 468 + } 469 + } 470 + return false; 471 + }, 472 + // @method once(…): this 473 + // Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed. 474 + once: function(types, fn, context) { 475 + if (typeof types === "object") { 476 + for (var type in types) { 477 + this._on(type, types[type], fn, true); 478 + } 479 + } else { 480 + types = splitWords(types); 481 + for (var i = 0, len = types.length; i < len; i++) { 482 + this._on(types[i], fn, context, true); 483 + } 484 + } 485 + return this; 486 + }, 487 + // @method addEventParent(obj: Evented): this 488 + // Adds an event parent - an `Evented` that will receive propagated events 489 + addEventParent: function(obj) { 490 + this._eventParents = this._eventParents || {}; 491 + this._eventParents[stamp(obj)] = obj; 492 + return this; 493 + }, 494 + // @method removeEventParent(obj: Evented): this 495 + // Removes an event parent, so it will stop receiving propagated events 496 + removeEventParent: function(obj) { 497 + if (this._eventParents) { 498 + delete this._eventParents[stamp(obj)]; 499 + } 500 + return this; 501 + }, 502 + _propagateEvent: function(e) { 503 + for (var id in this._eventParents) { 504 + this._eventParents[id].fire(e.type, extend({ 505 + layer: e.target, 506 + propagatedFrom: e.target 507 + }, e), true); 508 + } 509 + } 510 + }; 511 + Events.addEventListener = Events.on; 512 + Events.removeEventListener = Events.clearAllEventListeners = Events.off; 513 + Events.addOneTimeEventListener = Events.once; 514 + Events.fireEvent = Events.fire; 515 + Events.hasEventListeners = Events.listens; 516 + var Evented = Class.extend(Events); 517 + function Point(x, y, round) { 518 + this.x = round ? Math.round(x) : x; 519 + this.y = round ? Math.round(y) : y; 520 + } 521 + var trunc = Math.trunc || function(v) { 522 + return v > 0 ? Math.floor(v) : Math.ceil(v); 523 + }; 524 + Point.prototype = { 525 + // @method clone(): Point 526 + // Returns a copy of the current point. 527 + clone: function() { 528 + return new Point(this.x, this.y); 529 + }, 530 + // @method add(otherPoint: Point): Point 531 + // Returns the result of addition of the current and the given points. 532 + add: function(point) { 533 + return this.clone()._add(toPoint(point)); 534 + }, 535 + _add: function(point) { 536 + this.x += point.x; 537 + this.y += point.y; 538 + return this; 539 + }, 540 + // @method subtract(otherPoint: Point): Point 541 + // Returns the result of subtraction of the given point from the current. 542 + subtract: function(point) { 543 + return this.clone()._subtract(toPoint(point)); 544 + }, 545 + _subtract: function(point) { 546 + this.x -= point.x; 547 + this.y -= point.y; 548 + return this; 549 + }, 550 + // @method divideBy(num: Number): Point 551 + // Returns the result of division of the current point by the given number. 552 + divideBy: function(num) { 553 + return this.clone()._divideBy(num); 554 + }, 555 + _divideBy: function(num) { 556 + this.x /= num; 557 + this.y /= num; 558 + return this; 559 + }, 560 + // @method multiplyBy(num: Number): Point 561 + // Returns the result of multiplication of the current point by the given number. 562 + multiplyBy: function(num) { 563 + return this.clone()._multiplyBy(num); 564 + }, 565 + _multiplyBy: function(num) { 566 + this.x *= num; 567 + this.y *= num; 568 + return this; 569 + }, 570 + // @method scaleBy(scale: Point): Point 571 + // Multiply each coordinate of the current point by each coordinate of 572 + // `scale`. In linear algebra terms, multiply the point by the 573 + // [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation) 574 + // defined by `scale`. 575 + scaleBy: function(point) { 576 + return new Point(this.x * point.x, this.y * point.y); 577 + }, 578 + // @method unscaleBy(scale: Point): Point 579 + // Inverse of `scaleBy`. Divide each coordinate of the current point by 580 + // each coordinate of `scale`. 581 + unscaleBy: function(point) { 582 + return new Point(this.x / point.x, this.y / point.y); 583 + }, 584 + // @method round(): Point 585 + // Returns a copy of the current point with rounded coordinates. 586 + round: function() { 587 + return this.clone()._round(); 588 + }, 589 + _round: function() { 590 + this.x = Math.round(this.x); 591 + this.y = Math.round(this.y); 592 + return this; 593 + }, 594 + // @method floor(): Point 595 + // Returns a copy of the current point with floored coordinates (rounded down). 596 + floor: function() { 597 + return this.clone()._floor(); 598 + }, 599 + _floor: function() { 600 + this.x = Math.floor(this.x); 601 + this.y = Math.floor(this.y); 602 + return this; 603 + }, 604 + // @method ceil(): Point 605 + // Returns a copy of the current point with ceiled coordinates (rounded up). 606 + ceil: function() { 607 + return this.clone()._ceil(); 608 + }, 609 + _ceil: function() { 610 + this.x = Math.ceil(this.x); 611 + this.y = Math.ceil(this.y); 612 + return this; 613 + }, 614 + // @method trunc(): Point 615 + // Returns a copy of the current point with truncated coordinates (rounded towards zero). 616 + trunc: function() { 617 + return this.clone()._trunc(); 618 + }, 619 + _trunc: function() { 620 + this.x = trunc(this.x); 621 + this.y = trunc(this.y); 622 + return this; 623 + }, 624 + // @method distanceTo(otherPoint: Point): Number 625 + // Returns the cartesian distance between the current and the given points. 626 + distanceTo: function(point) { 627 + point = toPoint(point); 628 + var x = point.x - this.x, y = point.y - this.y; 629 + return Math.sqrt(x * x + y * y); 630 + }, 631 + // @method equals(otherPoint: Point): Boolean 632 + // Returns `true` if the given point has the same coordinates. 633 + equals: function(point) { 634 + point = toPoint(point); 635 + return point.x === this.x && point.y === this.y; 636 + }, 637 + // @method contains(otherPoint: Point): Boolean 638 + // Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values). 639 + contains: function(point) { 640 + point = toPoint(point); 641 + return Math.abs(point.x) <= Math.abs(this.x) && Math.abs(point.y) <= Math.abs(this.y); 642 + }, 643 + // @method toString(): String 644 + // Returns a string representation of the point for debugging purposes. 645 + toString: function() { 646 + return "Point(" + formatNum(this.x) + ", " + formatNum(this.y) + ")"; 647 + } 648 + }; 649 + function toPoint(x, y, round) { 650 + if (x instanceof Point) { 651 + return x; 652 + } 653 + if (isArray(x)) { 654 + return new Point(x[0], x[1]); 655 + } 656 + if (x === void 0 || x === null) { 657 + return x; 658 + } 659 + if (typeof x === "object" && "x" in x && "y" in x) { 660 + return new Point(x.x, x.y); 661 + } 662 + return new Point(x, y, round); 663 + } 664 + function Bounds(a, b) { 665 + if (!a) { 666 + return; 667 + } 668 + var points = b ? [a, b] : a; 669 + for (var i = 0, len = points.length; i < len; i++) { 670 + this.extend(points[i]); 671 + } 672 + } 673 + Bounds.prototype = { 674 + // @method extend(point: Point): this 675 + // Extends the bounds to contain the given point. 676 + // @alternative 677 + // @method extend(otherBounds: Bounds): this 678 + // Extend the bounds to contain the given bounds 679 + extend: function(obj) { 680 + var min2, max2; 681 + if (!obj) { 682 + return this; 683 + } 684 + if (obj instanceof Point || typeof obj[0] === "number" || "x" in obj) { 685 + min2 = max2 = toPoint(obj); 686 + } else { 687 + obj = toBounds(obj); 688 + min2 = obj.min; 689 + max2 = obj.max; 690 + if (!min2 || !max2) { 691 + return this; 692 + } 693 + } 694 + if (!this.min && !this.max) { 695 + this.min = min2.clone(); 696 + this.max = max2.clone(); 697 + } else { 698 + this.min.x = Math.min(min2.x, this.min.x); 699 + this.max.x = Math.max(max2.x, this.max.x); 700 + this.min.y = Math.min(min2.y, this.min.y); 701 + this.max.y = Math.max(max2.y, this.max.y); 702 + } 703 + return this; 704 + }, 705 + // @method getCenter(round?: Boolean): Point 706 + // Returns the center point of the bounds. 707 + getCenter: function(round) { 708 + return toPoint( 709 + (this.min.x + this.max.x) / 2, 710 + (this.min.y + this.max.y) / 2, 711 + round 712 + ); 713 + }, 714 + // @method getBottomLeft(): Point 715 + // Returns the bottom-left point of the bounds. 716 + getBottomLeft: function() { 717 + return toPoint(this.min.x, this.max.y); 718 + }, 719 + // @method getTopRight(): Point 720 + // Returns the top-right point of the bounds. 721 + getTopRight: function() { 722 + return toPoint(this.max.x, this.min.y); 723 + }, 724 + // @method getTopLeft(): Point 725 + // Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)). 726 + getTopLeft: function() { 727 + return this.min; 728 + }, 729 + // @method getBottomRight(): Point 730 + // Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)). 731 + getBottomRight: function() { 732 + return this.max; 733 + }, 734 + // @method getSize(): Point 735 + // Returns the size of the given bounds 736 + getSize: function() { 737 + return this.max.subtract(this.min); 738 + }, 739 + // @method contains(otherBounds: Bounds): Boolean 740 + // Returns `true` if the rectangle contains the given one. 741 + // @alternative 742 + // @method contains(point: Point): Boolean 743 + // Returns `true` if the rectangle contains the given point. 744 + contains: function(obj) { 745 + var min, max; 746 + if (typeof obj[0] === "number" || obj instanceof Point) { 747 + obj = toPoint(obj); 748 + } else { 749 + obj = toBounds(obj); 750 + } 751 + if (obj instanceof Bounds) { 752 + min = obj.min; 753 + max = obj.max; 754 + } else { 755 + min = max = obj; 756 + } 757 + return min.x >= this.min.x && max.x <= this.max.x && min.y >= this.min.y && max.y <= this.max.y; 758 + }, 759 + // @method intersects(otherBounds: Bounds): Boolean 760 + // Returns `true` if the rectangle intersects the given bounds. Two bounds 761 + // intersect if they have at least one point in common. 762 + intersects: function(bounds) { 763 + bounds = toBounds(bounds); 764 + var min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xIntersects = max2.x >= min.x && min2.x <= max.x, yIntersects = max2.y >= min.y && min2.y <= max.y; 765 + return xIntersects && yIntersects; 766 + }, 767 + // @method overlaps(otherBounds: Bounds): Boolean 768 + // Returns `true` if the rectangle overlaps the given bounds. Two bounds 769 + // overlap if their intersection is an area. 770 + overlaps: function(bounds) { 771 + bounds = toBounds(bounds); 772 + var min = this.min, max = this.max, min2 = bounds.min, max2 = bounds.max, xOverlaps = max2.x > min.x && min2.x < max.x, yOverlaps = max2.y > min.y && min2.y < max.y; 773 + return xOverlaps && yOverlaps; 774 + }, 775 + // @method isValid(): Boolean 776 + // Returns `true` if the bounds are properly initialized. 777 + isValid: function() { 778 + return !!(this.min && this.max); 779 + }, 780 + // @method pad(bufferRatio: Number): Bounds 781 + // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 782 + // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 783 + // Negative values will retract the bounds. 784 + pad: function(bufferRatio) { 785 + var min = this.min, max = this.max, heightBuffer = Math.abs(min.x - max.x) * bufferRatio, widthBuffer = Math.abs(min.y - max.y) * bufferRatio; 786 + return toBounds( 787 + toPoint(min.x - heightBuffer, min.y - widthBuffer), 788 + toPoint(max.x + heightBuffer, max.y + widthBuffer) 789 + ); 790 + }, 791 + // @method equals(otherBounds: Bounds): Boolean 792 + // Returns `true` if the rectangle is equivalent to the given bounds. 793 + equals: function(bounds) { 794 + if (!bounds) { 795 + return false; 796 + } 797 + bounds = toBounds(bounds); 798 + return this.min.equals(bounds.getTopLeft()) && this.max.equals(bounds.getBottomRight()); 799 + } 800 + }; 801 + function toBounds(a, b) { 802 + if (!a || a instanceof Bounds) { 803 + return a; 804 + } 805 + return new Bounds(a, b); 806 + } 807 + function LatLngBounds(corner1, corner2) { 808 + if (!corner1) { 809 + return; 810 + } 811 + var latlngs = corner2 ? [corner1, corner2] : corner1; 812 + for (var i = 0, len = latlngs.length; i < len; i++) { 813 + this.extend(latlngs[i]); 814 + } 815 + } 816 + LatLngBounds.prototype = { 817 + // @method extend(latlng: LatLng): this 818 + // Extend the bounds to contain the given point 819 + // @alternative 820 + // @method extend(otherBounds: LatLngBounds): this 821 + // Extend the bounds to contain the given bounds 822 + extend: function(obj) { 823 + var sw = this._southWest, ne = this._northEast, sw2, ne2; 824 + if (obj instanceof LatLng) { 825 + sw2 = obj; 826 + ne2 = obj; 827 + } else if (obj instanceof LatLngBounds) { 828 + sw2 = obj._southWest; 829 + ne2 = obj._northEast; 830 + if (!sw2 || !ne2) { 831 + return this; 832 + } 833 + } else { 834 + return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this; 835 + } 836 + if (!sw && !ne) { 837 + this._southWest = new LatLng(sw2.lat, sw2.lng); 838 + this._northEast = new LatLng(ne2.lat, ne2.lng); 839 + } else { 840 + sw.lat = Math.min(sw2.lat, sw.lat); 841 + sw.lng = Math.min(sw2.lng, sw.lng); 842 + ne.lat = Math.max(ne2.lat, ne.lat); 843 + ne.lng = Math.max(ne2.lng, ne.lng); 844 + } 845 + return this; 846 + }, 847 + // @method pad(bufferRatio: Number): LatLngBounds 848 + // Returns bounds created by extending or retracting the current bounds by a given ratio in each direction. 849 + // For example, a ratio of 0.5 extends the bounds by 50% in each direction. 850 + // Negative values will retract the bounds. 851 + pad: function(bufferRatio) { 852 + var sw = this._southWest, ne = this._northEast, heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio, widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio; 853 + return new LatLngBounds( 854 + new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer), 855 + new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer) 856 + ); 857 + }, 858 + // @method getCenter(): LatLng 859 + // Returns the center point of the bounds. 860 + getCenter: function() { 861 + return new LatLng( 862 + (this._southWest.lat + this._northEast.lat) / 2, 863 + (this._southWest.lng + this._northEast.lng) / 2 864 + ); 865 + }, 866 + // @method getSouthWest(): LatLng 867 + // Returns the south-west point of the bounds. 868 + getSouthWest: function() { 869 + return this._southWest; 870 + }, 871 + // @method getNorthEast(): LatLng 872 + // Returns the north-east point of the bounds. 873 + getNorthEast: function() { 874 + return this._northEast; 875 + }, 876 + // @method getNorthWest(): LatLng 877 + // Returns the north-west point of the bounds. 878 + getNorthWest: function() { 879 + return new LatLng(this.getNorth(), this.getWest()); 880 + }, 881 + // @method getSouthEast(): LatLng 882 + // Returns the south-east point of the bounds. 883 + getSouthEast: function() { 884 + return new LatLng(this.getSouth(), this.getEast()); 885 + }, 886 + // @method getWest(): Number 887 + // Returns the west longitude of the bounds 888 + getWest: function() { 889 + return this._southWest.lng; 890 + }, 891 + // @method getSouth(): Number 892 + // Returns the south latitude of the bounds 893 + getSouth: function() { 894 + return this._southWest.lat; 895 + }, 896 + // @method getEast(): Number 897 + // Returns the east longitude of the bounds 898 + getEast: function() { 899 + return this._northEast.lng; 900 + }, 901 + // @method getNorth(): Number 902 + // Returns the north latitude of the bounds 903 + getNorth: function() { 904 + return this._northEast.lat; 905 + }, 906 + // @method contains(otherBounds: LatLngBounds): Boolean 907 + // Returns `true` if the rectangle contains the given one. 908 + // @alternative 909 + // @method contains (latlng: LatLng): Boolean 910 + // Returns `true` if the rectangle contains the given point. 911 + contains: function(obj) { 912 + if (typeof obj[0] === "number" || obj instanceof LatLng || "lat" in obj) { 913 + obj = toLatLng(obj); 914 + } else { 915 + obj = toLatLngBounds(obj); 916 + } 917 + var sw = this._southWest, ne = this._northEast, sw2, ne2; 918 + if (obj instanceof LatLngBounds) { 919 + sw2 = obj.getSouthWest(); 920 + ne2 = obj.getNorthEast(); 921 + } else { 922 + sw2 = ne2 = obj; 923 + } 924 + return sw2.lat >= sw.lat && ne2.lat <= ne.lat && sw2.lng >= sw.lng && ne2.lng <= ne.lng; 925 + }, 926 + // @method intersects(otherBounds: LatLngBounds): Boolean 927 + // Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common. 928 + intersects: function(bounds) { 929 + bounds = toLatLngBounds(bounds); 930 + var sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latIntersects = ne2.lat >= sw.lat && sw2.lat <= ne.lat, lngIntersects = ne2.lng >= sw.lng && sw2.lng <= ne.lng; 931 + return latIntersects && lngIntersects; 932 + }, 933 + // @method overlaps(otherBounds: LatLngBounds): Boolean 934 + // Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area. 935 + overlaps: function(bounds) { 936 + bounds = toLatLngBounds(bounds); 937 + var sw = this._southWest, ne = this._northEast, sw2 = bounds.getSouthWest(), ne2 = bounds.getNorthEast(), latOverlaps = ne2.lat > sw.lat && sw2.lat < ne.lat, lngOverlaps = ne2.lng > sw.lng && sw2.lng < ne.lng; 938 + return latOverlaps && lngOverlaps; 939 + }, 940 + // @method toBBoxString(): String 941 + // Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data. 942 + toBBoxString: function() { 943 + return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(","); 944 + }, 945 + // @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean 946 + // Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number. 947 + equals: function(bounds, maxMargin) { 948 + if (!bounds) { 949 + return false; 950 + } 951 + bounds = toLatLngBounds(bounds); 952 + return this._southWest.equals(bounds.getSouthWest(), maxMargin) && this._northEast.equals(bounds.getNorthEast(), maxMargin); 953 + }, 954 + // @method isValid(): Boolean 955 + // Returns `true` if the bounds are properly initialized. 956 + isValid: function() { 957 + return !!(this._southWest && this._northEast); 958 + } 959 + }; 960 + function toLatLngBounds(a, b) { 961 + if (a instanceof LatLngBounds) { 962 + return a; 963 + } 964 + return new LatLngBounds(a, b); 965 + } 966 + function LatLng(lat, lng, alt) { 967 + if (isNaN(lat) || isNaN(lng)) { 968 + throw new Error("Invalid LatLng object: (" + lat + ", " + lng + ")"); 969 + } 970 + this.lat = +lat; 971 + this.lng = +lng; 972 + if (alt !== void 0) { 973 + this.alt = +alt; 974 + } 975 + } 976 + LatLng.prototype = { 977 + // @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean 978 + // Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number. 979 + equals: function(obj, maxMargin) { 980 + if (!obj) { 981 + return false; 982 + } 983 + obj = toLatLng(obj); 984 + var margin = Math.max( 985 + Math.abs(this.lat - obj.lat), 986 + Math.abs(this.lng - obj.lng) 987 + ); 988 + return margin <= (maxMargin === void 0 ? 1e-9 : maxMargin); 989 + }, 990 + // @method toString(): String 991 + // Returns a string representation of the point (for debugging purposes). 992 + toString: function(precision) { 993 + return "LatLng(" + formatNum(this.lat, precision) + ", " + formatNum(this.lng, precision) + ")"; 994 + }, 995 + // @method distanceTo(otherLatLng: LatLng): Number 996 + // Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines). 997 + distanceTo: function(other) { 998 + return Earth.distance(this, toLatLng(other)); 999 + }, 1000 + // @method wrap(): LatLng 1001 + // Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees. 1002 + wrap: function() { 1003 + return Earth.wrapLatLng(this); 1004 + }, 1005 + // @method toBounds(sizeInMeters: Number): LatLngBounds 1006 + // Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`. 1007 + toBounds: function(sizeInMeters) { 1008 + var latAccuracy = 180 * sizeInMeters / 40075017, lngAccuracy = latAccuracy / Math.cos(Math.PI / 180 * this.lat); 1009 + return toLatLngBounds( 1010 + [this.lat - latAccuracy, this.lng - lngAccuracy], 1011 + [this.lat + latAccuracy, this.lng + lngAccuracy] 1012 + ); 1013 + }, 1014 + clone: function() { 1015 + return new LatLng(this.lat, this.lng, this.alt); 1016 + } 1017 + }; 1018 + function toLatLng(a, b, c) { 1019 + if (a instanceof LatLng) { 1020 + return a; 1021 + } 1022 + if (isArray(a) && typeof a[0] !== "object") { 1023 + if (a.length === 3) { 1024 + return new LatLng(a[0], a[1], a[2]); 1025 + } 1026 + if (a.length === 2) { 1027 + return new LatLng(a[0], a[1]); 1028 + } 1029 + return null; 1030 + } 1031 + if (a === void 0 || a === null) { 1032 + return a; 1033 + } 1034 + if (typeof a === "object" && "lat" in a) { 1035 + return new LatLng(a.lat, "lng" in a ? a.lng : a.lon, a.alt); 1036 + } 1037 + if (b === void 0) { 1038 + return null; 1039 + } 1040 + return new LatLng(a, b, c); 1041 + } 1042 + var CRS = { 1043 + // @method latLngToPoint(latlng: LatLng, zoom: Number): Point 1044 + // Projects geographical coordinates into pixel coordinates for a given zoom. 1045 + latLngToPoint: function(latlng, zoom2) { 1046 + var projectedPoint = this.projection.project(latlng), scale2 = this.scale(zoom2); 1047 + return this.transformation._transform(projectedPoint, scale2); 1048 + }, 1049 + // @method pointToLatLng(point: Point, zoom: Number): LatLng 1050 + // The inverse of `latLngToPoint`. Projects pixel coordinates on a given 1051 + // zoom into geographical coordinates. 1052 + pointToLatLng: function(point, zoom2) { 1053 + var scale2 = this.scale(zoom2), untransformedPoint = this.transformation.untransform(point, scale2); 1054 + return this.projection.unproject(untransformedPoint); 1055 + }, 1056 + // @method project(latlng: LatLng): Point 1057 + // Projects geographical coordinates into coordinates in units accepted for 1058 + // this CRS (e.g. meters for EPSG:3857, for passing it to WMS services). 1059 + project: function(latlng) { 1060 + return this.projection.project(latlng); 1061 + }, 1062 + // @method unproject(point: Point): LatLng 1063 + // Given a projected coordinate returns the corresponding LatLng. 1064 + // The inverse of `project`. 1065 + unproject: function(point) { 1066 + return this.projection.unproject(point); 1067 + }, 1068 + // @method scale(zoom: Number): Number 1069 + // Returns the scale used when transforming projected coordinates into 1070 + // pixel coordinates for a particular zoom. For example, it returns 1071 + // `256 * 2^zoom` for Mercator-based CRS. 1072 + scale: function(zoom2) { 1073 + return 256 * Math.pow(2, zoom2); 1074 + }, 1075 + // @method zoom(scale: Number): Number 1076 + // Inverse of `scale()`, returns the zoom level corresponding to a scale 1077 + // factor of `scale`. 1078 + zoom: function(scale2) { 1079 + return Math.log(scale2 / 256) / Math.LN2; 1080 + }, 1081 + // @method getProjectedBounds(zoom: Number): Bounds 1082 + // Returns the projection's bounds scaled and transformed for the provided `zoom`. 1083 + getProjectedBounds: function(zoom2) { 1084 + if (this.infinite) { 1085 + return null; 1086 + } 1087 + var b = this.projection.bounds, s = this.scale(zoom2), min = this.transformation.transform(b.min, s), max = this.transformation.transform(b.max, s); 1088 + return new Bounds(min, max); 1089 + }, 1090 + // @method distance(latlng1: LatLng, latlng2: LatLng): Number 1091 + // Returns the distance between two geographical coordinates. 1092 + // @property code: String 1093 + // Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`) 1094 + // 1095 + // @property wrapLng: Number[] 1096 + // An array of two numbers defining whether the longitude (horizontal) coordinate 1097 + // axis wraps around a given range and how. Defaults to `[-180, 180]` in most 1098 + // geographical CRSs. If `undefined`, the longitude axis does not wrap around. 1099 + // 1100 + // @property wrapLat: Number[] 1101 + // Like `wrapLng`, but for the latitude (vertical) axis. 1102 + // wrapLng: [min, max], 1103 + // wrapLat: [min, max], 1104 + // @property infinite: Boolean 1105 + // If true, the coordinate space will be unbounded (infinite in both axes) 1106 + infinite: false, 1107 + // @method wrapLatLng(latlng: LatLng): LatLng 1108 + // Returns a `LatLng` where lat and lng has been wrapped according to the 1109 + // CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds. 1110 + wrapLatLng: function(latlng) { 1111 + var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng, lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat, alt = latlng.alt; 1112 + return new LatLng(lat, lng, alt); 1113 + }, 1114 + // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 1115 + // Returns a `LatLngBounds` with the same size as the given one, ensuring 1116 + // that its center is within the CRS's bounds. 1117 + // Only accepts actual `L.LatLngBounds` instances, not arrays. 1118 + wrapLatLngBounds: function(bounds) { 1119 + var center = bounds.getCenter(), newCenter = this.wrapLatLng(center), latShift = center.lat - newCenter.lat, lngShift = center.lng - newCenter.lng; 1120 + if (latShift === 0 && lngShift === 0) { 1121 + return bounds; 1122 + } 1123 + var sw = bounds.getSouthWest(), ne = bounds.getNorthEast(), newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift), newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift); 1124 + return new LatLngBounds(newSw, newNe); 1125 + } 1126 + }; 1127 + var Earth = extend({}, CRS, { 1128 + wrapLng: [-180, 180], 1129 + // Mean Earth Radius, as recommended for use by 1130 + // the International Union of Geodesy and Geophysics, 1131 + // see https://rosettacode.org/wiki/Haversine_formula 1132 + R: 6371e3, 1133 + // distance between two geographical points using spherical law of cosines approximation 1134 + distance: function(latlng1, latlng2) { 1135 + var rad = Math.PI / 180, lat1 = latlng1.lat * rad, lat2 = latlng2.lat * rad, sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2), sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2), a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon, c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); 1136 + return this.R * c; 1137 + } 1138 + }); 1139 + var earthRadius = 6378137; 1140 + var SphericalMercator = { 1141 + R: earthRadius, 1142 + MAX_LATITUDE: 85.0511287798, 1143 + project: function(latlng) { 1144 + var d = Math.PI / 180, max = this.MAX_LATITUDE, lat = Math.max(Math.min(max, latlng.lat), -max), sin = Math.sin(lat * d); 1145 + return new Point( 1146 + this.R * latlng.lng * d, 1147 + this.R * Math.log((1 + sin) / (1 - sin)) / 2 1148 + ); 1149 + }, 1150 + unproject: function(point) { 1151 + var d = 180 / Math.PI; 1152 + return new LatLng( 1153 + (2 * Math.atan(Math.exp(point.y / this.R)) - Math.PI / 2) * d, 1154 + point.x * d / this.R 1155 + ); 1156 + }, 1157 + bounds: function() { 1158 + var d = earthRadius * Math.PI; 1159 + return new Bounds([-d, -d], [d, d]); 1160 + }() 1161 + }; 1162 + function Transformation(a, b, c, d) { 1163 + if (isArray(a)) { 1164 + this._a = a[0]; 1165 + this._b = a[1]; 1166 + this._c = a[2]; 1167 + this._d = a[3]; 1168 + return; 1169 + } 1170 + this._a = a; 1171 + this._b = b; 1172 + this._c = c; 1173 + this._d = d; 1174 + } 1175 + Transformation.prototype = { 1176 + // @method transform(point: Point, scale?: Number): Point 1177 + // Returns a transformed point, optionally multiplied by the given scale. 1178 + // Only accepts actual `L.Point` instances, not arrays. 1179 + transform: function(point, scale2) { 1180 + return this._transform(point.clone(), scale2); 1181 + }, 1182 + // destructive transform (faster) 1183 + _transform: function(point, scale2) { 1184 + scale2 = scale2 || 1; 1185 + point.x = scale2 * (this._a * point.x + this._b); 1186 + point.y = scale2 * (this._c * point.y + this._d); 1187 + return point; 1188 + }, 1189 + // @method untransform(point: Point, scale?: Number): Point 1190 + // Returns the reverse transformation of the given point, optionally divided 1191 + // by the given scale. Only accepts actual `L.Point` instances, not arrays. 1192 + untransform: function(point, scale2) { 1193 + scale2 = scale2 || 1; 1194 + return new Point( 1195 + (point.x / scale2 - this._b) / this._a, 1196 + (point.y / scale2 - this._d) / this._c 1197 + ); 1198 + } 1199 + }; 1200 + function toTransformation(a, b, c, d) { 1201 + return new Transformation(a, b, c, d); 1202 + } 1203 + var EPSG3857 = extend({}, Earth, { 1204 + code: "EPSG:3857", 1205 + projection: SphericalMercator, 1206 + transformation: function() { 1207 + var scale2 = 0.5 / (Math.PI * SphericalMercator.R); 1208 + return toTransformation(scale2, 0.5, -scale2, 0.5); 1209 + }() 1210 + }); 1211 + var EPSG900913 = extend({}, EPSG3857, { 1212 + code: "EPSG:900913" 1213 + }); 1214 + function svgCreate(name) { 1215 + return document.createElementNS("http://www.w3.org/2000/svg", name); 1216 + } 1217 + function pointsToPath(rings, closed) { 1218 + var str = "", i, j, len, len2, points, p; 1219 + for (i = 0, len = rings.length; i < len; i++) { 1220 + points = rings[i]; 1221 + for (j = 0, len2 = points.length; j < len2; j++) { 1222 + p = points[j]; 1223 + str += (j ? "L" : "M") + p.x + " " + p.y; 1224 + } 1225 + str += closed ? Browser.svg ? "z" : "x" : ""; 1226 + } 1227 + return str || "M0 0"; 1228 + } 1229 + var style = document.documentElement.style; 1230 + var ie = "ActiveXObject" in window; 1231 + var ielt9 = ie && !document.addEventListener; 1232 + var edge = "msLaunchUri" in navigator && !("documentMode" in document); 1233 + var webkit = userAgentContains("webkit"); 1234 + var android = userAgentContains("android"); 1235 + var android23 = userAgentContains("android 2") || userAgentContains("android 3"); 1236 + var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); 1237 + var androidStock = android && userAgentContains("Google") && webkitVer < 537 && !("AudioNode" in window); 1238 + var opera = !!window.opera; 1239 + var chrome = !edge && userAgentContains("chrome"); 1240 + var gecko = userAgentContains("gecko") && !webkit && !opera && !ie; 1241 + var safari = !chrome && userAgentContains("safari"); 1242 + var phantom = userAgentContains("phantom"); 1243 + var opera12 = "OTransition" in style; 1244 + var win = navigator.platform.indexOf("Win") === 0; 1245 + var ie3d = ie && "transition" in style; 1246 + var webkit3d = "WebKitCSSMatrix" in window && "m11" in new window.WebKitCSSMatrix() && !android23; 1247 + var gecko3d = "MozPerspective" in style; 1248 + var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom; 1249 + var mobile = typeof orientation !== "undefined" || userAgentContains("mobile"); 1250 + var mobileWebkit = mobile && webkit; 1251 + var mobileWebkit3d = mobile && webkit3d; 1252 + var msPointer = !window.PointerEvent && window.MSPointerEvent; 1253 + var pointer = !!(window.PointerEvent || msPointer); 1254 + var touchNative = "ontouchstart" in window || !!window.TouchEvent; 1255 + var touch = !window.L_NO_TOUCH && (touchNative || pointer); 1256 + var mobileOpera = mobile && opera; 1257 + var mobileGecko = mobile && gecko; 1258 + var retina = (window.devicePixelRatio || window.screen.deviceXDPI / window.screen.logicalXDPI) > 1; 1259 + var passiveEvents = function() { 1260 + var supportsPassiveOption = false; 1261 + try { 1262 + var opts = Object.defineProperty({}, "passive", { 1263 + get: function() { 1264 + supportsPassiveOption = true; 1265 + } 1266 + }); 1267 + window.addEventListener("testPassiveEventSupport", falseFn, opts); 1268 + window.removeEventListener("testPassiveEventSupport", falseFn, opts); 1269 + } catch (e) { 1270 + } 1271 + return supportsPassiveOption; 1272 + }(); 1273 + var canvas$1 = function() { 1274 + return !!document.createElement("canvas").getContext; 1275 + }(); 1276 + var svg$1 = !!(document.createElementNS && svgCreate("svg").createSVGRect); 1277 + var inlineSvg = !!svg$1 && function() { 1278 + var div = document.createElement("div"); 1279 + div.innerHTML = "<svg/>"; 1280 + return (div.firstChild && div.firstChild.namespaceURI) === "http://www.w3.org/2000/svg"; 1281 + }(); 1282 + var vml = !svg$1 && function() { 1283 + try { 1284 + var div = document.createElement("div"); 1285 + div.innerHTML = '<v:shape adj="1"/>'; 1286 + var shape = div.firstChild; 1287 + shape.style.behavior = "url(#default#VML)"; 1288 + return shape && typeof shape.adj === "object"; 1289 + } catch (e) { 1290 + return false; 1291 + } 1292 + }(); 1293 + var mac = navigator.platform.indexOf("Mac") === 0; 1294 + var linux = navigator.platform.indexOf("Linux") === 0; 1295 + function userAgentContains(str) { 1296 + return navigator.userAgent.toLowerCase().indexOf(str) >= 0; 1297 + } 1298 + var Browser = { 1299 + ie, 1300 + ielt9, 1301 + edge, 1302 + webkit, 1303 + android, 1304 + android23, 1305 + androidStock, 1306 + opera, 1307 + chrome, 1308 + gecko, 1309 + safari, 1310 + phantom, 1311 + opera12, 1312 + win, 1313 + ie3d, 1314 + webkit3d, 1315 + gecko3d, 1316 + any3d, 1317 + mobile, 1318 + mobileWebkit, 1319 + mobileWebkit3d, 1320 + msPointer, 1321 + pointer, 1322 + touch, 1323 + touchNative, 1324 + mobileOpera, 1325 + mobileGecko, 1326 + retina, 1327 + passiveEvents, 1328 + canvas: canvas$1, 1329 + svg: svg$1, 1330 + vml, 1331 + inlineSvg, 1332 + mac, 1333 + linux 1334 + }; 1335 + var POINTER_DOWN = Browser.msPointer ? "MSPointerDown" : "pointerdown"; 1336 + var POINTER_MOVE = Browser.msPointer ? "MSPointerMove" : "pointermove"; 1337 + var POINTER_UP = Browser.msPointer ? "MSPointerUp" : "pointerup"; 1338 + var POINTER_CANCEL = Browser.msPointer ? "MSPointerCancel" : "pointercancel"; 1339 + var pEvent = { 1340 + touchstart: POINTER_DOWN, 1341 + touchmove: POINTER_MOVE, 1342 + touchend: POINTER_UP, 1343 + touchcancel: POINTER_CANCEL 1344 + }; 1345 + var handle = { 1346 + touchstart: _onPointerStart, 1347 + touchmove: _handlePointer, 1348 + touchend: _handlePointer, 1349 + touchcancel: _handlePointer 1350 + }; 1351 + var _pointers = {}; 1352 + var _pointerDocListener = false; 1353 + function addPointerListener(obj, type, handler) { 1354 + if (type === "touchstart") { 1355 + _addPointerDocListener(); 1356 + } 1357 + if (!handle[type]) { 1358 + console.warn("wrong event specified:", type); 1359 + return falseFn; 1360 + } 1361 + handler = handle[type].bind(this, handler); 1362 + obj.addEventListener(pEvent[type], handler, false); 1363 + return handler; 1364 + } 1365 + function removePointerListener(obj, type, handler) { 1366 + if (!pEvent[type]) { 1367 + console.warn("wrong event specified:", type); 1368 + return; 1369 + } 1370 + obj.removeEventListener(pEvent[type], handler, false); 1371 + } 1372 + function _globalPointerDown(e) { 1373 + _pointers[e.pointerId] = e; 1374 + } 1375 + function _globalPointerMove(e) { 1376 + if (_pointers[e.pointerId]) { 1377 + _pointers[e.pointerId] = e; 1378 + } 1379 + } 1380 + function _globalPointerUp(e) { 1381 + delete _pointers[e.pointerId]; 1382 + } 1383 + function _addPointerDocListener() { 1384 + if (!_pointerDocListener) { 1385 + document.addEventListener(POINTER_DOWN, _globalPointerDown, true); 1386 + document.addEventListener(POINTER_MOVE, _globalPointerMove, true); 1387 + document.addEventListener(POINTER_UP, _globalPointerUp, true); 1388 + document.addEventListener(POINTER_CANCEL, _globalPointerUp, true); 1389 + _pointerDocListener = true; 1390 + } 1391 + } 1392 + function _handlePointer(handler, e) { 1393 + if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || "mouse")) { 1394 + return; 1395 + } 1396 + e.touches = []; 1397 + for (var i in _pointers) { 1398 + e.touches.push(_pointers[i]); 1399 + } 1400 + e.changedTouches = [e]; 1401 + handler(e); 1402 + } 1403 + function _onPointerStart(handler, e) { 1404 + if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) { 1405 + preventDefault(e); 1406 + } 1407 + _handlePointer(handler, e); 1408 + } 1409 + function makeDblclick(event) { 1410 + var newEvent = {}, prop, i; 1411 + for (i in event) { 1412 + prop = event[i]; 1413 + newEvent[i] = prop && prop.bind ? prop.bind(event) : prop; 1414 + } 1415 + event = newEvent; 1416 + newEvent.type = "dblclick"; 1417 + newEvent.detail = 2; 1418 + newEvent.isTrusted = false; 1419 + newEvent._simulated = true; 1420 + return newEvent; 1421 + } 1422 + var delay = 200; 1423 + function addDoubleTapListener(obj, handler) { 1424 + obj.addEventListener("dblclick", handler); 1425 + var last = 0, detail; 1426 + function simDblclick(e) { 1427 + if (e.detail !== 1) { 1428 + detail = e.detail; 1429 + return; 1430 + } 1431 + if (e.pointerType === "mouse" || e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents) { 1432 + return; 1433 + } 1434 + var path = getPropagationPath(e); 1435 + if (path.some(function(el) { 1436 + return el instanceof HTMLLabelElement && el.attributes.for; 1437 + }) && !path.some(function(el) { 1438 + return el instanceof HTMLInputElement || el instanceof HTMLSelectElement; 1439 + })) { 1440 + return; 1441 + } 1442 + var now = Date.now(); 1443 + if (now - last <= delay) { 1444 + detail++; 1445 + if (detail === 2) { 1446 + handler(makeDblclick(e)); 1447 + } 1448 + } else { 1449 + detail = 1; 1450 + } 1451 + last = now; 1452 + } 1453 + obj.addEventListener("click", simDblclick); 1454 + return { 1455 + dblclick: handler, 1456 + simDblclick 1457 + }; 1458 + } 1459 + function removeDoubleTapListener(obj, handlers) { 1460 + obj.removeEventListener("dblclick", handlers.dblclick); 1461 + obj.removeEventListener("click", handlers.simDblclick); 1462 + } 1463 + var TRANSFORM = testProp( 1464 + ["transform", "webkitTransform", "OTransform", "MozTransform", "msTransform"] 1465 + ); 1466 + var TRANSITION = testProp( 1467 + ["webkitTransition", "transition", "OTransition", "MozTransition", "msTransition"] 1468 + ); 1469 + var TRANSITION_END = TRANSITION === "webkitTransition" || TRANSITION === "OTransition" ? TRANSITION + "End" : "transitionend"; 1470 + function get(id) { 1471 + return typeof id === "string" ? document.getElementById(id) : id; 1472 + } 1473 + function getStyle(el, style2) { 1474 + var value = el.style[style2] || el.currentStyle && el.currentStyle[style2]; 1475 + if ((!value || value === "auto") && document.defaultView) { 1476 + var css = document.defaultView.getComputedStyle(el, null); 1477 + value = css ? css[style2] : null; 1478 + } 1479 + return value === "auto" ? null : value; 1480 + } 1481 + function create$1(tagName, className, container) { 1482 + var el = document.createElement(tagName); 1483 + el.className = className || ""; 1484 + if (container) { 1485 + container.appendChild(el); 1486 + } 1487 + return el; 1488 + } 1489 + function remove(el) { 1490 + var parent = el.parentNode; 1491 + if (parent) { 1492 + parent.removeChild(el); 1493 + } 1494 + } 1495 + function empty(el) { 1496 + while (el.firstChild) { 1497 + el.removeChild(el.firstChild); 1498 + } 1499 + } 1500 + function toFront(el) { 1501 + var parent = el.parentNode; 1502 + if (parent && parent.lastChild !== el) { 1503 + parent.appendChild(el); 1504 + } 1505 + } 1506 + function toBack(el) { 1507 + var parent = el.parentNode; 1508 + if (parent && parent.firstChild !== el) { 1509 + parent.insertBefore(el, parent.firstChild); 1510 + } 1511 + } 1512 + function hasClass(el, name) { 1513 + if (el.classList !== void 0) { 1514 + return el.classList.contains(name); 1515 + } 1516 + var className = getClass(el); 1517 + return className.length > 0 && new RegExp("(^|\\s)" + name + "(\\s|$)").test(className); 1518 + } 1519 + function addClass(el, name) { 1520 + if (el.classList !== void 0) { 1521 + var classes = splitWords(name); 1522 + for (var i = 0, len = classes.length; i < len; i++) { 1523 + el.classList.add(classes[i]); 1524 + } 1525 + } else if (!hasClass(el, name)) { 1526 + var className = getClass(el); 1527 + setClass(el, (className ? className + " " : "") + name); 1528 + } 1529 + } 1530 + function removeClass(el, name) { 1531 + if (el.classList !== void 0) { 1532 + el.classList.remove(name); 1533 + } else { 1534 + setClass(el, trim((" " + getClass(el) + " ").replace(" " + name + " ", " "))); 1535 + } 1536 + } 1537 + function setClass(el, name) { 1538 + if (el.className.baseVal === void 0) { 1539 + el.className = name; 1540 + } else { 1541 + el.className.baseVal = name; 1542 + } 1543 + } 1544 + function getClass(el) { 1545 + if (el.correspondingElement) { 1546 + el = el.correspondingElement; 1547 + } 1548 + return el.className.baseVal === void 0 ? el.className : el.className.baseVal; 1549 + } 1550 + function setOpacity(el, value) { 1551 + if ("opacity" in el.style) { 1552 + el.style.opacity = value; 1553 + } else if ("filter" in el.style) { 1554 + _setOpacityIE(el, value); 1555 + } 1556 + } 1557 + function _setOpacityIE(el, value) { 1558 + var filter = false, filterName = "DXImageTransform.Microsoft.Alpha"; 1559 + try { 1560 + filter = el.filters.item(filterName); 1561 + } catch (e) { 1562 + if (value === 1) { 1563 + return; 1564 + } 1565 + } 1566 + value = Math.round(value * 100); 1567 + if (filter) { 1568 + filter.Enabled = value !== 100; 1569 + filter.Opacity = value; 1570 + } else { 1571 + el.style.filter += " progid:" + filterName + "(opacity=" + value + ")"; 1572 + } 1573 + } 1574 + function testProp(props) { 1575 + var style2 = document.documentElement.style; 1576 + for (var i = 0; i < props.length; i++) { 1577 + if (props[i] in style2) { 1578 + return props[i]; 1579 + } 1580 + } 1581 + return false; 1582 + } 1583 + function setTransform(el, offset, scale2) { 1584 + var pos = offset || new Point(0, 0); 1585 + el.style[TRANSFORM] = (Browser.ie3d ? "translate(" + pos.x + "px," + pos.y + "px)" : "translate3d(" + pos.x + "px," + pos.y + "px,0)") + (scale2 ? " scale(" + scale2 + ")" : ""); 1586 + } 1587 + function setPosition(el, point) { 1588 + el._leaflet_pos = point; 1589 + if (Browser.any3d) { 1590 + setTransform(el, point); 1591 + } else { 1592 + el.style.left = point.x + "px"; 1593 + el.style.top = point.y + "px"; 1594 + } 1595 + } 1596 + function getPosition(el) { 1597 + return el._leaflet_pos || new Point(0, 0); 1598 + } 1599 + var disableTextSelection; 1600 + var enableTextSelection; 1601 + var _userSelect; 1602 + if ("onselectstart" in document) { 1603 + disableTextSelection = function() { 1604 + on(window, "selectstart", preventDefault); 1605 + }; 1606 + enableTextSelection = function() { 1607 + off(window, "selectstart", preventDefault); 1608 + }; 1609 + } else { 1610 + var userSelectProperty = testProp( 1611 + ["userSelect", "WebkitUserSelect", "OUserSelect", "MozUserSelect", "msUserSelect"] 1612 + ); 1613 + disableTextSelection = function() { 1614 + if (userSelectProperty) { 1615 + var style2 = document.documentElement.style; 1616 + _userSelect = style2[userSelectProperty]; 1617 + style2[userSelectProperty] = "none"; 1618 + } 1619 + }; 1620 + enableTextSelection = function() { 1621 + if (userSelectProperty) { 1622 + document.documentElement.style[userSelectProperty] = _userSelect; 1623 + _userSelect = void 0; 1624 + } 1625 + }; 1626 + } 1627 + function disableImageDrag() { 1628 + on(window, "dragstart", preventDefault); 1629 + } 1630 + function enableImageDrag() { 1631 + off(window, "dragstart", preventDefault); 1632 + } 1633 + var _outlineElement, _outlineStyle; 1634 + function preventOutline(element) { 1635 + while (element.tabIndex === -1) { 1636 + element = element.parentNode; 1637 + } 1638 + if (!element.style) { 1639 + return; 1640 + } 1641 + restoreOutline(); 1642 + _outlineElement = element; 1643 + _outlineStyle = element.style.outlineStyle; 1644 + element.style.outlineStyle = "none"; 1645 + on(window, "keydown", restoreOutline); 1646 + } 1647 + function restoreOutline() { 1648 + if (!_outlineElement) { 1649 + return; 1650 + } 1651 + _outlineElement.style.outlineStyle = _outlineStyle; 1652 + _outlineElement = void 0; 1653 + _outlineStyle = void 0; 1654 + off(window, "keydown", restoreOutline); 1655 + } 1656 + function getSizedParentNode(element) { 1657 + do { 1658 + element = element.parentNode; 1659 + } while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body); 1660 + return element; 1661 + } 1662 + function getScale(element) { 1663 + var rect = element.getBoundingClientRect(); 1664 + return { 1665 + x: rect.width / element.offsetWidth || 1, 1666 + y: rect.height / element.offsetHeight || 1, 1667 + boundingClientRect: rect 1668 + }; 1669 + } 1670 + var DomUtil = { 1671 + __proto__: null, 1672 + TRANSFORM, 1673 + TRANSITION, 1674 + TRANSITION_END, 1675 + get, 1676 + getStyle, 1677 + create: create$1, 1678 + remove, 1679 + empty, 1680 + toFront, 1681 + toBack, 1682 + hasClass, 1683 + addClass, 1684 + removeClass, 1685 + setClass, 1686 + getClass, 1687 + setOpacity, 1688 + testProp, 1689 + setTransform, 1690 + setPosition, 1691 + getPosition, 1692 + get disableTextSelection() { 1693 + return disableTextSelection; 1694 + }, 1695 + get enableTextSelection() { 1696 + return enableTextSelection; 1697 + }, 1698 + disableImageDrag, 1699 + enableImageDrag, 1700 + preventOutline, 1701 + restoreOutline, 1702 + getSizedParentNode, 1703 + getScale 1704 + }; 1705 + function on(obj, types, fn, context) { 1706 + if (types && typeof types === "object") { 1707 + for (var type in types) { 1708 + addOne(obj, type, types[type], fn); 1709 + } 1710 + } else { 1711 + types = splitWords(types); 1712 + for (var i = 0, len = types.length; i < len; i++) { 1713 + addOne(obj, types[i], fn, context); 1714 + } 1715 + } 1716 + return this; 1717 + } 1718 + var eventsKey = "_leaflet_events"; 1719 + function off(obj, types, fn, context) { 1720 + if (arguments.length === 1) { 1721 + batchRemove(obj); 1722 + delete obj[eventsKey]; 1723 + } else if (types && typeof types === "object") { 1724 + for (var type in types) { 1725 + removeOne(obj, type, types[type], fn); 1726 + } 1727 + } else { 1728 + types = splitWords(types); 1729 + if (arguments.length === 2) { 1730 + batchRemove(obj, function(type2) { 1731 + return indexOf(types, type2) !== -1; 1732 + }); 1733 + } else { 1734 + for (var i = 0, len = types.length; i < len; i++) { 1735 + removeOne(obj, types[i], fn, context); 1736 + } 1737 + } 1738 + } 1739 + return this; 1740 + } 1741 + function batchRemove(obj, filterFn) { 1742 + for (var id in obj[eventsKey]) { 1743 + var type = id.split(/\d/)[0]; 1744 + if (!filterFn || filterFn(type)) { 1745 + removeOne(obj, type, null, null, id); 1746 + } 1747 + } 1748 + } 1749 + var mouseSubst = { 1750 + mouseenter: "mouseover", 1751 + mouseleave: "mouseout", 1752 + wheel: !("onwheel" in window) && "mousewheel" 1753 + }; 1754 + function addOne(obj, type, fn, context) { 1755 + var id = type + stamp(fn) + (context ? "_" + stamp(context) : ""); 1756 + if (obj[eventsKey] && obj[eventsKey][id]) { 1757 + return this; 1758 + } 1759 + var handler = function(e) { 1760 + return fn.call(context || obj, e || window.event); 1761 + }; 1762 + var originalHandler = handler; 1763 + if (!Browser.touchNative && Browser.pointer && type.indexOf("touch") === 0) { 1764 + handler = addPointerListener(obj, type, handler); 1765 + } else if (Browser.touch && type === "dblclick") { 1766 + handler = addDoubleTapListener(obj, handler); 1767 + } else if ("addEventListener" in obj) { 1768 + if (type === "touchstart" || type === "touchmove" || type === "wheel" || type === "mousewheel") { 1769 + obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? { passive: false } : false); 1770 + } else if (type === "mouseenter" || type === "mouseleave") { 1771 + handler = function(e) { 1772 + e = e || window.event; 1773 + if (isExternalTarget(obj, e)) { 1774 + originalHandler(e); 1775 + } 1776 + }; 1777 + obj.addEventListener(mouseSubst[type], handler, false); 1778 + } else { 1779 + obj.addEventListener(type, originalHandler, false); 1780 + } 1781 + } else { 1782 + obj.attachEvent("on" + type, handler); 1783 + } 1784 + obj[eventsKey] = obj[eventsKey] || {}; 1785 + obj[eventsKey][id] = handler; 1786 + } 1787 + function removeOne(obj, type, fn, context, id) { 1788 + id = id || type + stamp(fn) + (context ? "_" + stamp(context) : ""); 1789 + var handler = obj[eventsKey] && obj[eventsKey][id]; 1790 + if (!handler) { 1791 + return this; 1792 + } 1793 + if (!Browser.touchNative && Browser.pointer && type.indexOf("touch") === 0) { 1794 + removePointerListener(obj, type, handler); 1795 + } else if (Browser.touch && type === "dblclick") { 1796 + removeDoubleTapListener(obj, handler); 1797 + } else if ("removeEventListener" in obj) { 1798 + obj.removeEventListener(mouseSubst[type] || type, handler, false); 1799 + } else { 1800 + obj.detachEvent("on" + type, handler); 1801 + } 1802 + obj[eventsKey][id] = null; 1803 + } 1804 + function stopPropagation(e) { 1805 + if (e.stopPropagation) { 1806 + e.stopPropagation(); 1807 + } else if (e.originalEvent) { 1808 + e.originalEvent._stopped = true; 1809 + } else { 1810 + e.cancelBubble = true; 1811 + } 1812 + return this; 1813 + } 1814 + function disableScrollPropagation(el) { 1815 + addOne(el, "wheel", stopPropagation); 1816 + return this; 1817 + } 1818 + function disableClickPropagation(el) { 1819 + on(el, "mousedown touchstart dblclick contextmenu", stopPropagation); 1820 + el["_leaflet_disable_click"] = true; 1821 + return this; 1822 + } 1823 + function preventDefault(e) { 1824 + if (e.preventDefault) { 1825 + e.preventDefault(); 1826 + } else { 1827 + e.returnValue = false; 1828 + } 1829 + return this; 1830 + } 1831 + function stop(e) { 1832 + preventDefault(e); 1833 + stopPropagation(e); 1834 + return this; 1835 + } 1836 + function getPropagationPath(ev) { 1837 + if (ev.composedPath) { 1838 + return ev.composedPath(); 1839 + } 1840 + var path = []; 1841 + var el = ev.target; 1842 + while (el) { 1843 + path.push(el); 1844 + el = el.parentNode; 1845 + } 1846 + return path; 1847 + } 1848 + function getMousePosition(e, container) { 1849 + if (!container) { 1850 + return new Point(e.clientX, e.clientY); 1851 + } 1852 + var scale2 = getScale(container), offset = scale2.boundingClientRect; 1853 + return new Point( 1854 + // offset.left/top values are in page scale (like clientX/Y), 1855 + // whereas clientLeft/Top (border width) values are the original values (before CSS scale applies). 1856 + (e.clientX - offset.left) / scale2.x - container.clientLeft, 1857 + (e.clientY - offset.top) / scale2.y - container.clientTop 1858 + ); 1859 + } 1860 + var wheelPxFactor = Browser.linux && Browser.chrome ? window.devicePixelRatio : Browser.mac ? window.devicePixelRatio * 3 : window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1; 1861 + function getWheelDelta(e) { 1862 + return Browser.edge ? e.wheelDeltaY / 2 : ( 1863 + // Don't trust window-geometry-based delta 1864 + e.deltaY && e.deltaMode === 0 ? -e.deltaY / wheelPxFactor : ( 1865 + // Pixels 1866 + e.deltaY && e.deltaMode === 1 ? -e.deltaY * 20 : ( 1867 + // Lines 1868 + e.deltaY && e.deltaMode === 2 ? -e.deltaY * 60 : ( 1869 + // Pages 1870 + e.deltaX || e.deltaZ ? 0 : ( 1871 + // Skip horizontal/depth wheel events 1872 + e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : ( 1873 + // Legacy IE pixels 1874 + e.detail && Math.abs(e.detail) < 32765 ? -e.detail * 20 : ( 1875 + // Legacy Moz lines 1876 + e.detail ? e.detail / -32765 * 60 : ( 1877 + // Legacy Moz pages 1878 + 0 1879 + ) 1880 + ) 1881 + ) 1882 + ) 1883 + ) 1884 + ) 1885 + ) 1886 + ); 1887 + } 1888 + function isExternalTarget(el, e) { 1889 + var related = e.relatedTarget; 1890 + if (!related) { 1891 + return true; 1892 + } 1893 + try { 1894 + while (related && related !== el) { 1895 + related = related.parentNode; 1896 + } 1897 + } catch (err) { 1898 + return false; 1899 + } 1900 + return related !== el; 1901 + } 1902 + var DomEvent = { 1903 + __proto__: null, 1904 + on, 1905 + off, 1906 + stopPropagation, 1907 + disableScrollPropagation, 1908 + disableClickPropagation, 1909 + preventDefault, 1910 + stop, 1911 + getPropagationPath, 1912 + getMousePosition, 1913 + getWheelDelta, 1914 + isExternalTarget, 1915 + addListener: on, 1916 + removeListener: off 1917 + }; 1918 + var PosAnimation = Evented.extend({ 1919 + // @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number) 1920 + // Run an animation of a given element to a new position, optionally setting 1921 + // duration in seconds (`0.25` by default) and easing linearity factor (3rd 1922 + // argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1), 1923 + // `0.5` by default). 1924 + run: function(el, newPos, duration, easeLinearity) { 1925 + this.stop(); 1926 + this._el = el; 1927 + this._inProgress = true; 1928 + this._duration = duration || 0.25; 1929 + this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2); 1930 + this._startPos = getPosition(el); 1931 + this._offset = newPos.subtract(this._startPos); 1932 + this._startTime = +/* @__PURE__ */ new Date(); 1933 + this.fire("start"); 1934 + this._animate(); 1935 + }, 1936 + // @method stop() 1937 + // Stops the animation (if currently running). 1938 + stop: function() { 1939 + if (!this._inProgress) { 1940 + return; 1941 + } 1942 + this._step(true); 1943 + this._complete(); 1944 + }, 1945 + _animate: function() { 1946 + this._animId = requestAnimFrame(this._animate, this); 1947 + this._step(); 1948 + }, 1949 + _step: function(round) { 1950 + var elapsed = +/* @__PURE__ */ new Date() - this._startTime, duration = this._duration * 1e3; 1951 + if (elapsed < duration) { 1952 + this._runFrame(this._easeOut(elapsed / duration), round); 1953 + } else { 1954 + this._runFrame(1); 1955 + this._complete(); 1956 + } 1957 + }, 1958 + _runFrame: function(progress, round) { 1959 + var pos = this._startPos.add(this._offset.multiplyBy(progress)); 1960 + if (round) { 1961 + pos._round(); 1962 + } 1963 + setPosition(this._el, pos); 1964 + this.fire("step"); 1965 + }, 1966 + _complete: function() { 1967 + cancelAnimFrame(this._animId); 1968 + this._inProgress = false; 1969 + this.fire("end"); 1970 + }, 1971 + _easeOut: function(t) { 1972 + return 1 - Math.pow(1 - t, this._easeOutPower); 1973 + } 1974 + }); 1975 + var Map2 = Evented.extend({ 1976 + options: { 1977 + // @section Map State Options 1978 + // @option crs: CRS = L.CRS.EPSG3857 1979 + // The [Coordinate Reference System](#crs) to use. Don't change this if you're not 1980 + // sure what it means. 1981 + crs: EPSG3857, 1982 + // @option center: LatLng = undefined 1983 + // Initial geographic center of the map 1984 + center: void 0, 1985 + // @option zoom: Number = undefined 1986 + // Initial map zoom level 1987 + zoom: void 0, 1988 + // @option minZoom: Number = * 1989 + // Minimum zoom level of the map. 1990 + // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 1991 + // the lowest of their `minZoom` options will be used instead. 1992 + minZoom: void 0, 1993 + // @option maxZoom: Number = * 1994 + // Maximum zoom level of the map. 1995 + // If not specified and at least one `GridLayer` or `TileLayer` is in the map, 1996 + // the highest of their `maxZoom` options will be used instead. 1997 + maxZoom: void 0, 1998 + // @option layers: Layer[] = [] 1999 + // Array of layers that will be added to the map initially 2000 + layers: [], 2001 + // @option maxBounds: LatLngBounds = null 2002 + // When this option is set, the map restricts the view to the given 2003 + // geographical bounds, bouncing the user back if the user tries to pan 2004 + // outside the view. To set the restriction dynamically, use 2005 + // [`setMaxBounds`](#map-setmaxbounds) method. 2006 + maxBounds: void 0, 2007 + // @option renderer: Renderer = * 2008 + // The default method for drawing vector layers on the map. `L.SVG` 2009 + // or `L.Canvas` by default depending on browser support. 2010 + renderer: void 0, 2011 + // @section Animation Options 2012 + // @option zoomAnimation: Boolean = true 2013 + // Whether the map zoom animation is enabled. By default it's enabled 2014 + // in all browsers that support CSS3 Transitions except Android. 2015 + zoomAnimation: true, 2016 + // @option zoomAnimationThreshold: Number = 4 2017 + // Won't animate zoom if the zoom difference exceeds this value. 2018 + zoomAnimationThreshold: 4, 2019 + // @option fadeAnimation: Boolean = true 2020 + // Whether the tile fade animation is enabled. By default it's enabled 2021 + // in all browsers that support CSS3 Transitions except Android. 2022 + fadeAnimation: true, 2023 + // @option markerZoomAnimation: Boolean = true 2024 + // Whether markers animate their zoom with the zoom animation, if disabled 2025 + // they will disappear for the length of the animation. By default it's 2026 + // enabled in all browsers that support CSS3 Transitions except Android. 2027 + markerZoomAnimation: true, 2028 + // @option transform3DLimit: Number = 2^23 2029 + // Defines the maximum size of a CSS translation transform. The default 2030 + // value should not be changed unless a web browser positions layers in 2031 + // the wrong place after doing a large `panBy`. 2032 + transform3DLimit: 8388608, 2033 + // Precision limit of a 32-bit float 2034 + // @section Interaction Options 2035 + // @option zoomSnap: Number = 1 2036 + // Forces the map's zoom level to always be a multiple of this, particularly 2037 + // right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom. 2038 + // By default, the zoom level snaps to the nearest integer; lower values 2039 + // (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0` 2040 + // means the zoom level will not be snapped after `fitBounds` or a pinch-zoom. 2041 + zoomSnap: 1, 2042 + // @option zoomDelta: Number = 1 2043 + // Controls how much the map's zoom level will change after a 2044 + // [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+` 2045 + // or `-` on the keyboard, or using the [zoom controls](#control-zoom). 2046 + // Values smaller than `1` (e.g. `0.5`) allow for greater granularity. 2047 + zoomDelta: 1, 2048 + // @option trackResize: Boolean = true 2049 + // Whether the map automatically handles browser window resize to update itself. 2050 + trackResize: true 2051 + }, 2052 + initialize: function(id, options) { 2053 + options = setOptions(this, options); 2054 + this._handlers = []; 2055 + this._layers = {}; 2056 + this._zoomBoundLayers = {}; 2057 + this._sizeChanged = true; 2058 + this._initContainer(id); 2059 + this._initLayout(); 2060 + this._onResize = bind(this._onResize, this); 2061 + this._initEvents(); 2062 + if (options.maxBounds) { 2063 + this.setMaxBounds(options.maxBounds); 2064 + } 2065 + if (options.zoom !== void 0) { 2066 + this._zoom = this._limitZoom(options.zoom); 2067 + } 2068 + if (options.center && options.zoom !== void 0) { 2069 + this.setView(toLatLng(options.center), options.zoom, { reset: true }); 2070 + } 2071 + this.callInitHooks(); 2072 + this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera && this.options.zoomAnimation; 2073 + if (this._zoomAnimated) { 2074 + this._createAnimProxy(); 2075 + on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this); 2076 + } 2077 + this._addLayers(this.options.layers); 2078 + }, 2079 + // @section Methods for modifying map state 2080 + // @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this 2081 + // Sets the view of the map (geographical center and zoom) with the given 2082 + // animation options. 2083 + setView: function(center, zoom2, options) { 2084 + zoom2 = zoom2 === void 0 ? this._zoom : this._limitZoom(zoom2); 2085 + center = this._limitCenter(toLatLng(center), zoom2, this.options.maxBounds); 2086 + options = options || {}; 2087 + this._stop(); 2088 + if (this._loaded && !options.reset && options !== true) { 2089 + if (options.animate !== void 0) { 2090 + options.zoom = extend({ animate: options.animate }, options.zoom); 2091 + options.pan = extend({ animate: options.animate, duration: options.duration }, options.pan); 2092 + } 2093 + var moved = this._zoom !== zoom2 ? this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom2, options.zoom) : this._tryAnimatedPan(center, options.pan); 2094 + if (moved) { 2095 + clearTimeout(this._sizeTimer); 2096 + return this; 2097 + } 2098 + } 2099 + this._resetView(center, zoom2, options.pan && options.pan.noMoveStart); 2100 + return this; 2101 + }, 2102 + // @method setZoom(zoom: Number, options?: Zoom/pan options): this 2103 + // Sets the zoom of the map. 2104 + setZoom: function(zoom2, options) { 2105 + if (!this._loaded) { 2106 + this._zoom = zoom2; 2107 + return this; 2108 + } 2109 + return this.setView(this.getCenter(), zoom2, { zoom: options }); 2110 + }, 2111 + // @method zoomIn(delta?: Number, options?: Zoom options): this 2112 + // Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 2113 + zoomIn: function(delta, options) { 2114 + delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 2115 + return this.setZoom(this._zoom + delta, options); 2116 + }, 2117 + // @method zoomOut(delta?: Number, options?: Zoom options): this 2118 + // Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default). 2119 + zoomOut: function(delta, options) { 2120 + delta = delta || (Browser.any3d ? this.options.zoomDelta : 1); 2121 + return this.setZoom(this._zoom - delta, options); 2122 + }, 2123 + // @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this 2124 + // Zooms the map while keeping a specified geographical point on the map 2125 + // stationary (e.g. used internally for scroll zoom and double-click zoom). 2126 + // @alternative 2127 + // @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this 2128 + // Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary. 2129 + setZoomAround: function(latlng, zoom2, options) { 2130 + var scale2 = this.getZoomScale(zoom2), viewHalf = this.getSize().divideBy(2), containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng), centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale2), newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset)); 2131 + return this.setView(newCenter, zoom2, { zoom: options }); 2132 + }, 2133 + _getBoundsCenterZoom: function(bounds, options) { 2134 + options = options || {}; 2135 + bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds); 2136 + var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), zoom2 = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR)); 2137 + zoom2 = typeof options.maxZoom === "number" ? Math.min(options.maxZoom, zoom2) : zoom2; 2138 + if (zoom2 === Infinity) { 2139 + return { 2140 + center: bounds.getCenter(), 2141 + zoom: zoom2 2142 + }; 2143 + } 2144 + var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2), swPoint = this.project(bounds.getSouthWest(), zoom2), nePoint = this.project(bounds.getNorthEast(), zoom2), center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom2); 2145 + return { 2146 + center, 2147 + zoom: zoom2 2148 + }; 2149 + }, 2150 + // @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this 2151 + // Sets a map view that contains the given geographical bounds with the 2152 + // maximum zoom level possible. 2153 + fitBounds: function(bounds, options) { 2154 + bounds = toLatLngBounds(bounds); 2155 + if (!bounds.isValid()) { 2156 + throw new Error("Bounds are not valid."); 2157 + } 2158 + var target = this._getBoundsCenterZoom(bounds, options); 2159 + return this.setView(target.center, target.zoom, options); 2160 + }, 2161 + // @method fitWorld(options?: fitBounds options): this 2162 + // Sets a map view that mostly contains the whole world with the maximum 2163 + // zoom level possible. 2164 + fitWorld: function(options) { 2165 + return this.fitBounds([[-90, -180], [90, 180]], options); 2166 + }, 2167 + // @method panTo(latlng: LatLng, options?: Pan options): this 2168 + // Pans the map to a given center. 2169 + panTo: function(center, options) { 2170 + return this.setView(center, this._zoom, { pan: options }); 2171 + }, 2172 + // @method panBy(offset: Point, options?: Pan options): this 2173 + // Pans the map by a given number of pixels (animated). 2174 + panBy: function(offset, options) { 2175 + offset = toPoint(offset).round(); 2176 + options = options || {}; 2177 + if (!offset.x && !offset.y) { 2178 + return this.fire("moveend"); 2179 + } 2180 + if (options.animate !== true && !this.getSize().contains(offset)) { 2181 + this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom()); 2182 + return this; 2183 + } 2184 + if (!this._panAnim) { 2185 + this._panAnim = new PosAnimation(); 2186 + this._panAnim.on({ 2187 + "step": this._onPanTransitionStep, 2188 + "end": this._onPanTransitionEnd 2189 + }, this); 2190 + } 2191 + if (!options.noMoveStart) { 2192 + this.fire("movestart"); 2193 + } 2194 + if (options.animate !== false) { 2195 + addClass(this._mapPane, "leaflet-pan-anim"); 2196 + var newPos = this._getMapPanePos().subtract(offset).round(); 2197 + this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity); 2198 + } else { 2199 + this._rawPanBy(offset); 2200 + this.fire("move").fire("moveend"); 2201 + } 2202 + return this; 2203 + }, 2204 + // @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this 2205 + // Sets the view of the map (geographical center and zoom) performing a smooth 2206 + // pan-zoom animation. 2207 + flyTo: function(targetCenter, targetZoom, options) { 2208 + options = options || {}; 2209 + if (options.animate === false || !Browser.any3d) { 2210 + return this.setView(targetCenter, targetZoom, options); 2211 + } 2212 + this._stop(); 2213 + var from = this.project(this.getCenter()), to = this.project(targetCenter), size = this.getSize(), startZoom = this._zoom; 2214 + targetCenter = toLatLng(targetCenter); 2215 + targetZoom = targetZoom === void 0 ? startZoom : targetZoom; 2216 + var w0 = Math.max(size.x, size.y), w1 = w0 * this.getZoomScale(startZoom, targetZoom), u1 = to.distanceTo(from) || 1, rho = 1.42, rho2 = rho * rho; 2217 + function r(i) { 2218 + var s1 = i ? -1 : 1, s2 = i ? w1 : w0, t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1, b1 = 2 * s2 * rho2 * u1, b = t1 / b1, sq = Math.sqrt(b * b + 1) - b; 2219 + var log = sq < 1e-9 ? -18 : Math.log(sq); 2220 + return log; 2221 + } 2222 + function sinh(n) { 2223 + return (Math.exp(n) - Math.exp(-n)) / 2; 2224 + } 2225 + function cosh(n) { 2226 + return (Math.exp(n) + Math.exp(-n)) / 2; 2227 + } 2228 + function tanh(n) { 2229 + return sinh(n) / cosh(n); 2230 + } 2231 + var r0 = r(0); 2232 + function w(s) { 2233 + return w0 * (cosh(r0) / cosh(r0 + rho * s)); 2234 + } 2235 + function u(s) { 2236 + return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; 2237 + } 2238 + function easeOut(t) { 2239 + return 1 - Math.pow(1 - t, 1.5); 2240 + } 2241 + var start = Date.now(), S = (r(1) - r0) / rho, duration = options.duration ? 1e3 * options.duration : 1e3 * S * 0.8; 2242 + function frame() { 2243 + var t = (Date.now() - start) / duration, s = easeOut(t) * S; 2244 + if (t <= 1) { 2245 + this._flyToFrame = requestAnimFrame(frame, this); 2246 + this._move( 2247 + this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom), 2248 + this.getScaleZoom(w0 / w(s), startZoom), 2249 + { flyTo: true } 2250 + ); 2251 + } else { 2252 + this._move(targetCenter, targetZoom)._moveEnd(true); 2253 + } 2254 + } 2255 + this._moveStart(true, options.noMoveStart); 2256 + frame.call(this); 2257 + return this; 2258 + }, 2259 + // @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this 2260 + // Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto), 2261 + // but takes a bounds parameter like [`fitBounds`](#map-fitbounds). 2262 + flyToBounds: function(bounds, options) { 2263 + var target = this._getBoundsCenterZoom(bounds, options); 2264 + return this.flyTo(target.center, target.zoom, options); 2265 + }, 2266 + // @method setMaxBounds(bounds: LatLngBounds): this 2267 + // Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option). 2268 + setMaxBounds: function(bounds) { 2269 + bounds = toLatLngBounds(bounds); 2270 + if (this.listens("moveend", this._panInsideMaxBounds)) { 2271 + this.off("moveend", this._panInsideMaxBounds); 2272 + } 2273 + if (!bounds.isValid()) { 2274 + this.options.maxBounds = null; 2275 + return this; 2276 + } 2277 + this.options.maxBounds = bounds; 2278 + if (this._loaded) { 2279 + this._panInsideMaxBounds(); 2280 + } 2281 + return this.on("moveend", this._panInsideMaxBounds); 2282 + }, 2283 + // @method setMinZoom(zoom: Number): this 2284 + // Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option). 2285 + setMinZoom: function(zoom2) { 2286 + var oldZoom = this.options.minZoom; 2287 + this.options.minZoom = zoom2; 2288 + if (this._loaded && oldZoom !== zoom2) { 2289 + this.fire("zoomlevelschange"); 2290 + if (this.getZoom() < this.options.minZoom) { 2291 + return this.setZoom(zoom2); 2292 + } 2293 + } 2294 + return this; 2295 + }, 2296 + // @method setMaxZoom(zoom: Number): this 2297 + // Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option). 2298 + setMaxZoom: function(zoom2) { 2299 + var oldZoom = this.options.maxZoom; 2300 + this.options.maxZoom = zoom2; 2301 + if (this._loaded && oldZoom !== zoom2) { 2302 + this.fire("zoomlevelschange"); 2303 + if (this.getZoom() > this.options.maxZoom) { 2304 + return this.setZoom(zoom2); 2305 + } 2306 + } 2307 + return this; 2308 + }, 2309 + // @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this 2310 + // Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any. 2311 + panInsideBounds: function(bounds, options) { 2312 + this._enforcingBounds = true; 2313 + var center = this.getCenter(), newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds)); 2314 + if (!center.equals(newCenter)) { 2315 + this.panTo(newCenter, options); 2316 + } 2317 + this._enforcingBounds = false; 2318 + return this; 2319 + }, 2320 + // @method panInside(latlng: LatLng, options?: padding options): this 2321 + // Pans the map the minimum amount to make the `latlng` visible. Use 2322 + // padding options to fit the display to more restricted bounds. 2323 + // If `latlng` is already within the (optionally padded) display bounds, 2324 + // the map will not be panned. 2325 + panInside: function(latlng, options) { 2326 + options = options || {}; 2327 + var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]), paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]), pixelCenter = this.project(this.getCenter()), pixelPoint = this.project(latlng), pixelBounds = this.getPixelBounds(), paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]), paddedSize = paddedBounds.getSize(); 2328 + if (!paddedBounds.contains(pixelPoint)) { 2329 + this._enforcingBounds = true; 2330 + var centerOffset = pixelPoint.subtract(paddedBounds.getCenter()); 2331 + var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize); 2332 + pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x; 2333 + pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y; 2334 + this.panTo(this.unproject(pixelCenter), options); 2335 + this._enforcingBounds = false; 2336 + } 2337 + return this; 2338 + }, 2339 + // @method invalidateSize(options: Zoom/pan options): this 2340 + // Checks if the map container size changed and updates the map if so — 2341 + // call it after you've changed the map size dynamically, also animating 2342 + // pan by default. If `options.pan` is `false`, panning will not occur. 2343 + // If `options.debounceMoveend` is `true`, it will delay `moveend` event so 2344 + // that it doesn't happen often even if the method is called many 2345 + // times in a row. 2346 + // @alternative 2347 + // @method invalidateSize(animate: Boolean): this 2348 + // Checks if the map container size changed and updates the map if so — 2349 + // call it after you've changed the map size dynamically, also animating 2350 + // pan by default. 2351 + invalidateSize: function(options) { 2352 + if (!this._loaded) { 2353 + return this; 2354 + } 2355 + options = extend({ 2356 + animate: false, 2357 + pan: true 2358 + }, options === true ? { animate: true } : options); 2359 + var oldSize = this.getSize(); 2360 + this._sizeChanged = true; 2361 + this._lastCenter = null; 2362 + var newSize = this.getSize(), oldCenter = oldSize.divideBy(2).round(), newCenter = newSize.divideBy(2).round(), offset = oldCenter.subtract(newCenter); 2363 + if (!offset.x && !offset.y) { 2364 + return this; 2365 + } 2366 + if (options.animate && options.pan) { 2367 + this.panBy(offset); 2368 + } else { 2369 + if (options.pan) { 2370 + this._rawPanBy(offset); 2371 + } 2372 + this.fire("move"); 2373 + if (options.debounceMoveend) { 2374 + clearTimeout(this._sizeTimer); 2375 + this._sizeTimer = setTimeout(bind(this.fire, this, "moveend"), 200); 2376 + } else { 2377 + this.fire("moveend"); 2378 + } 2379 + } 2380 + return this.fire("resize", { 2381 + oldSize, 2382 + newSize 2383 + }); 2384 + }, 2385 + // @section Methods for modifying map state 2386 + // @method stop(): this 2387 + // Stops the currently running `panTo` or `flyTo` animation, if any. 2388 + stop: function() { 2389 + this.setZoom(this._limitZoom(this._zoom)); 2390 + if (!this.options.zoomSnap) { 2391 + this.fire("viewreset"); 2392 + } 2393 + return this._stop(); 2394 + }, 2395 + // @section Geolocation methods 2396 + // @method locate(options?: Locate options): this 2397 + // Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound) 2398 + // event with location data on success or a [`locationerror`](#map-locationerror) event on failure, 2399 + // and optionally sets the map view to the user's location with respect to 2400 + // detection accuracy (or to the world view if geolocation failed). 2401 + // Note that, if your page doesn't use HTTPS, this method will fail in 2402 + // modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins)) 2403 + // See `Locate options` for more details. 2404 + locate: function(options) { 2405 + options = this._locateOptions = extend({ 2406 + timeout: 1e4, 2407 + watch: false 2408 + // setView: false 2409 + // maxZoom: <Number> 2410 + // maximumAge: 0 2411 + // enableHighAccuracy: false 2412 + }, options); 2413 + if (!("geolocation" in navigator)) { 2414 + this._handleGeolocationError({ 2415 + code: 0, 2416 + message: "Geolocation not supported." 2417 + }); 2418 + return this; 2419 + } 2420 + var onResponse = bind(this._handleGeolocationResponse, this), onError = bind(this._handleGeolocationError, this); 2421 + if (options.watch) { 2422 + this._locationWatchId = navigator.geolocation.watchPosition(onResponse, onError, options); 2423 + } else { 2424 + navigator.geolocation.getCurrentPosition(onResponse, onError, options); 2425 + } 2426 + return this; 2427 + }, 2428 + // @method stopLocate(): this 2429 + // Stops watching location previously initiated by `map.locate({watch: true})` 2430 + // and aborts resetting the map view if map.locate was called with 2431 + // `{setView: true}`. 2432 + stopLocate: function() { 2433 + if (navigator.geolocation && navigator.geolocation.clearWatch) { 2434 + navigator.geolocation.clearWatch(this._locationWatchId); 2435 + } 2436 + if (this._locateOptions) { 2437 + this._locateOptions.setView = false; 2438 + } 2439 + return this; 2440 + }, 2441 + _handleGeolocationError: function(error) { 2442 + if (!this._container._leaflet_id) { 2443 + return; 2444 + } 2445 + var c = error.code, message = error.message || (c === 1 ? "permission denied" : c === 2 ? "position unavailable" : "timeout"); 2446 + if (this._locateOptions.setView && !this._loaded) { 2447 + this.fitWorld(); 2448 + } 2449 + this.fire("locationerror", { 2450 + code: c, 2451 + message: "Geolocation error: " + message + "." 2452 + }); 2453 + }, 2454 + _handleGeolocationResponse: function(pos) { 2455 + if (!this._container._leaflet_id) { 2456 + return; 2457 + } 2458 + var lat = pos.coords.latitude, lng = pos.coords.longitude, latlng = new LatLng(lat, lng), bounds = latlng.toBounds(pos.coords.accuracy * 2), options = this._locateOptions; 2459 + if (options.setView) { 2460 + var zoom2 = this.getBoundsZoom(bounds); 2461 + this.setView(latlng, options.maxZoom ? Math.min(zoom2, options.maxZoom) : zoom2); 2462 + } 2463 + var data = { 2464 + latlng, 2465 + bounds, 2466 + timestamp: pos.timestamp 2467 + }; 2468 + for (var i in pos.coords) { 2469 + if (typeof pos.coords[i] === "number") { 2470 + data[i] = pos.coords[i]; 2471 + } 2472 + } 2473 + this.fire("locationfound", data); 2474 + }, 2475 + // TODO Appropriate docs section? 2476 + // @section Other Methods 2477 + // @method addHandler(name: String, HandlerClass: Function): this 2478 + // Adds a new `Handler` to the map, given its name and constructor function. 2479 + addHandler: function(name, HandlerClass) { 2480 + if (!HandlerClass) { 2481 + return this; 2482 + } 2483 + var handler = this[name] = new HandlerClass(this); 2484 + this._handlers.push(handler); 2485 + if (this.options[name]) { 2486 + handler.enable(); 2487 + } 2488 + return this; 2489 + }, 2490 + // @method remove(): this 2491 + // Destroys the map and clears all related event listeners. 2492 + remove: function() { 2493 + this._initEvents(true); 2494 + if (this.options.maxBounds) { 2495 + this.off("moveend", this._panInsideMaxBounds); 2496 + } 2497 + if (this._containerId !== this._container._leaflet_id) { 2498 + throw new Error("Map container is being reused by another instance"); 2499 + } 2500 + try { 2501 + delete this._container._leaflet_id; 2502 + delete this._containerId; 2503 + } catch (e) { 2504 + this._container._leaflet_id = void 0; 2505 + this._containerId = void 0; 2506 + } 2507 + if (this._locationWatchId !== void 0) { 2508 + this.stopLocate(); 2509 + } 2510 + this._stop(); 2511 + remove(this._mapPane); 2512 + if (this._clearControlPos) { 2513 + this._clearControlPos(); 2514 + } 2515 + if (this._resizeRequest) { 2516 + cancelAnimFrame(this._resizeRequest); 2517 + this._resizeRequest = null; 2518 + } 2519 + this._clearHandlers(); 2520 + if (this._loaded) { 2521 + this.fire("unload"); 2522 + } 2523 + var i; 2524 + for (i in this._layers) { 2525 + this._layers[i].remove(); 2526 + } 2527 + for (i in this._panes) { 2528 + remove(this._panes[i]); 2529 + } 2530 + this._layers = []; 2531 + this._panes = []; 2532 + delete this._mapPane; 2533 + delete this._renderer; 2534 + return this; 2535 + }, 2536 + // @section Other Methods 2537 + // @method createPane(name: String, container?: HTMLElement): HTMLElement 2538 + // Creates a new [map pane](#map-pane) with the given name if it doesn't exist already, 2539 + // then returns it. The pane is created as a child of `container`, or 2540 + // as a child of the main map pane if not set. 2541 + createPane: function(name, container) { 2542 + var className = "leaflet-pane" + (name ? " leaflet-" + name.replace("Pane", "") + "-pane" : ""), pane = create$1("div", className, container || this._mapPane); 2543 + if (name) { 2544 + this._panes[name] = pane; 2545 + } 2546 + return pane; 2547 + }, 2548 + // @section Methods for Getting Map State 2549 + // @method getCenter(): LatLng 2550 + // Returns the geographical center of the map view 2551 + getCenter: function() { 2552 + this._checkIfLoaded(); 2553 + if (this._lastCenter && !this._moved()) { 2554 + return this._lastCenter.clone(); 2555 + } 2556 + return this.layerPointToLatLng(this._getCenterLayerPoint()); 2557 + }, 2558 + // @method getZoom(): Number 2559 + // Returns the current zoom level of the map view 2560 + getZoom: function() { 2561 + return this._zoom; 2562 + }, 2563 + // @method getBounds(): LatLngBounds 2564 + // Returns the geographical bounds visible in the current map view 2565 + getBounds: function() { 2566 + var bounds = this.getPixelBounds(), sw = this.unproject(bounds.getBottomLeft()), ne = this.unproject(bounds.getTopRight()); 2567 + return new LatLngBounds(sw, ne); 2568 + }, 2569 + // @method getMinZoom(): Number 2570 + // Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default. 2571 + getMinZoom: function() { 2572 + return this.options.minZoom === void 0 ? this._layersMinZoom || 0 : this.options.minZoom; 2573 + }, 2574 + // @method getMaxZoom(): Number 2575 + // Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers). 2576 + getMaxZoom: function() { 2577 + return this.options.maxZoom === void 0 ? this._layersMaxZoom === void 0 ? Infinity : this._layersMaxZoom : this.options.maxZoom; 2578 + }, 2579 + // @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number 2580 + // Returns the maximum zoom level on which the given bounds fit to the map 2581 + // view in its entirety. If `inside` (optional) is set to `true`, the method 2582 + // instead returns the minimum zoom level on which the map view fits into 2583 + // the given bounds in its entirety. 2584 + getBoundsZoom: function(bounds, inside, padding) { 2585 + bounds = toLatLngBounds(bounds); 2586 + padding = toPoint(padding || [0, 0]); 2587 + var zoom2 = this.getZoom() || 0, min = this.getMinZoom(), max = this.getMaxZoom(), nw = bounds.getNorthWest(), se = bounds.getSouthEast(), size = this.getSize().subtract(padding), boundsSize = toBounds(this.project(se, zoom2), this.project(nw, zoom2)).getSize(), snap = Browser.any3d ? this.options.zoomSnap : 1, scalex = size.x / boundsSize.x, scaley = size.y / boundsSize.y, scale2 = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley); 2588 + zoom2 = this.getScaleZoom(scale2, zoom2); 2589 + if (snap) { 2590 + zoom2 = Math.round(zoom2 / (snap / 100)) * (snap / 100); 2591 + zoom2 = inside ? Math.ceil(zoom2 / snap) * snap : Math.floor(zoom2 / snap) * snap; 2592 + } 2593 + return Math.max(min, Math.min(max, zoom2)); 2594 + }, 2595 + // @method getSize(): Point 2596 + // Returns the current size of the map container (in pixels). 2597 + getSize: function() { 2598 + if (!this._size || this._sizeChanged) { 2599 + this._size = new Point( 2600 + this._container.clientWidth || 0, 2601 + this._container.clientHeight || 0 2602 + ); 2603 + this._sizeChanged = false; 2604 + } 2605 + return this._size.clone(); 2606 + }, 2607 + // @method getPixelBounds(): Bounds 2608 + // Returns the bounds of the current map view in projected pixel 2609 + // coordinates (sometimes useful in layer and overlay implementations). 2610 + getPixelBounds: function(center, zoom2) { 2611 + var topLeftPoint = this._getTopLeftPoint(center, zoom2); 2612 + return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize())); 2613 + }, 2614 + // TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to 2615 + // the map pane? "left point of the map layer" can be confusing, specially 2616 + // since there can be negative offsets. 2617 + // @method getPixelOrigin(): Point 2618 + // Returns the projected pixel coordinates of the top left point of 2619 + // the map layer (useful in custom layer and overlay implementations). 2620 + getPixelOrigin: function() { 2621 + this._checkIfLoaded(); 2622 + return this._pixelOrigin; 2623 + }, 2624 + // @method getPixelWorldBounds(zoom?: Number): Bounds 2625 + // Returns the world's bounds in pixel coordinates for zoom level `zoom`. 2626 + // If `zoom` is omitted, the map's current zoom level is used. 2627 + getPixelWorldBounds: function(zoom2) { 2628 + return this.options.crs.getProjectedBounds(zoom2 === void 0 ? this.getZoom() : zoom2); 2629 + }, 2630 + // @section Other Methods 2631 + // @method getPane(pane: String|HTMLElement): HTMLElement 2632 + // Returns a [map pane](#map-pane), given its name or its HTML element (its identity). 2633 + getPane: function(pane) { 2634 + return typeof pane === "string" ? this._panes[pane] : pane; 2635 + }, 2636 + // @method getPanes(): Object 2637 + // Returns a plain object containing the names of all [panes](#map-pane) as keys and 2638 + // the panes as values. 2639 + getPanes: function() { 2640 + return this._panes; 2641 + }, 2642 + // @method getContainer: HTMLElement 2643 + // Returns the HTML element that contains the map. 2644 + getContainer: function() { 2645 + return this._container; 2646 + }, 2647 + // @section Conversion Methods 2648 + // @method getZoomScale(toZoom: Number, fromZoom: Number): Number 2649 + // Returns the scale factor to be applied to a map transition from zoom level 2650 + // `fromZoom` to `toZoom`. Used internally to help with zoom animations. 2651 + getZoomScale: function(toZoom, fromZoom) { 2652 + var crs = this.options.crs; 2653 + fromZoom = fromZoom === void 0 ? this._zoom : fromZoom; 2654 + return crs.scale(toZoom) / crs.scale(fromZoom); 2655 + }, 2656 + // @method getScaleZoom(scale: Number, fromZoom: Number): Number 2657 + // Returns the zoom level that the map would end up at, if it is at `fromZoom` 2658 + // level and everything is scaled by a factor of `scale`. Inverse of 2659 + // [`getZoomScale`](#map-getZoomScale). 2660 + getScaleZoom: function(scale2, fromZoom) { 2661 + var crs = this.options.crs; 2662 + fromZoom = fromZoom === void 0 ? this._zoom : fromZoom; 2663 + var zoom2 = crs.zoom(scale2 * crs.scale(fromZoom)); 2664 + return isNaN(zoom2) ? Infinity : zoom2; 2665 + }, 2666 + // @method project(latlng: LatLng, zoom: Number): Point 2667 + // Projects a geographical coordinate `LatLng` according to the projection 2668 + // of the map's CRS, then scales it according to `zoom` and the CRS's 2669 + // `Transformation`. The result is pixel coordinate relative to 2670 + // the CRS origin. 2671 + project: function(latlng, zoom2) { 2672 + zoom2 = zoom2 === void 0 ? this._zoom : zoom2; 2673 + return this.options.crs.latLngToPoint(toLatLng(latlng), zoom2); 2674 + }, 2675 + // @method unproject(point: Point, zoom: Number): LatLng 2676 + // Inverse of [`project`](#map-project). 2677 + unproject: function(point, zoom2) { 2678 + zoom2 = zoom2 === void 0 ? this._zoom : zoom2; 2679 + return this.options.crs.pointToLatLng(toPoint(point), zoom2); 2680 + }, 2681 + // @method layerPointToLatLng(point: Point): LatLng 2682 + // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 2683 + // returns the corresponding geographical coordinate (for the current zoom level). 2684 + layerPointToLatLng: function(point) { 2685 + var projectedPoint = toPoint(point).add(this.getPixelOrigin()); 2686 + return this.unproject(projectedPoint); 2687 + }, 2688 + // @method latLngToLayerPoint(latlng: LatLng): Point 2689 + // Given a geographical coordinate, returns the corresponding pixel coordinate 2690 + // relative to the [origin pixel](#map-getpixelorigin). 2691 + latLngToLayerPoint: function(latlng) { 2692 + var projectedPoint = this.project(toLatLng(latlng))._round(); 2693 + return projectedPoint._subtract(this.getPixelOrigin()); 2694 + }, 2695 + // @method wrapLatLng(latlng: LatLng): LatLng 2696 + // Returns a `LatLng` where `lat` and `lng` has been wrapped according to the 2697 + // map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the 2698 + // CRS's bounds. 2699 + // By default this means longitude is wrapped around the dateline so its 2700 + // value is between -180 and +180 degrees. 2701 + wrapLatLng: function(latlng) { 2702 + return this.options.crs.wrapLatLng(toLatLng(latlng)); 2703 + }, 2704 + // @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds 2705 + // Returns a `LatLngBounds` with the same size as the given one, ensuring that 2706 + // its center is within the CRS's bounds. 2707 + // By default this means the center longitude is wrapped around the dateline so its 2708 + // value is between -180 and +180 degrees, and the majority of the bounds 2709 + // overlaps the CRS's bounds. 2710 + wrapLatLngBounds: function(latlng) { 2711 + return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng)); 2712 + }, 2713 + // @method distance(latlng1: LatLng, latlng2: LatLng): Number 2714 + // Returns the distance between two geographical coordinates according to 2715 + // the map's CRS. By default this measures distance in meters. 2716 + distance: function(latlng1, latlng2) { 2717 + return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2)); 2718 + }, 2719 + // @method containerPointToLayerPoint(point: Point): Point 2720 + // Given a pixel coordinate relative to the map container, returns the corresponding 2721 + // pixel coordinate relative to the [origin pixel](#map-getpixelorigin). 2722 + containerPointToLayerPoint: function(point) { 2723 + return toPoint(point).subtract(this._getMapPanePos()); 2724 + }, 2725 + // @method layerPointToContainerPoint(point: Point): Point 2726 + // Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin), 2727 + // returns the corresponding pixel coordinate relative to the map container. 2728 + layerPointToContainerPoint: function(point) { 2729 + return toPoint(point).add(this._getMapPanePos()); 2730 + }, 2731 + // @method containerPointToLatLng(point: Point): LatLng 2732 + // Given a pixel coordinate relative to the map container, returns 2733 + // the corresponding geographical coordinate (for the current zoom level). 2734 + containerPointToLatLng: function(point) { 2735 + var layerPoint = this.containerPointToLayerPoint(toPoint(point)); 2736 + return this.layerPointToLatLng(layerPoint); 2737 + }, 2738 + // @method latLngToContainerPoint(latlng: LatLng): Point 2739 + // Given a geographical coordinate, returns the corresponding pixel coordinate 2740 + // relative to the map container. 2741 + latLngToContainerPoint: function(latlng) { 2742 + return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng))); 2743 + }, 2744 + // @method mouseEventToContainerPoint(ev: MouseEvent): Point 2745 + // Given a MouseEvent object, returns the pixel coordinate relative to the 2746 + // map container where the event took place. 2747 + mouseEventToContainerPoint: function(e) { 2748 + return getMousePosition(e, this._container); 2749 + }, 2750 + // @method mouseEventToLayerPoint(ev: MouseEvent): Point 2751 + // Given a MouseEvent object, returns the pixel coordinate relative to 2752 + // the [origin pixel](#map-getpixelorigin) where the event took place. 2753 + mouseEventToLayerPoint: function(e) { 2754 + return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e)); 2755 + }, 2756 + // @method mouseEventToLatLng(ev: MouseEvent): LatLng 2757 + // Given a MouseEvent object, returns geographical coordinate where the 2758 + // event took place. 2759 + mouseEventToLatLng: function(e) { 2760 + return this.layerPointToLatLng(this.mouseEventToLayerPoint(e)); 2761 + }, 2762 + // map initialization methods 2763 + _initContainer: function(id) { 2764 + var container = this._container = get(id); 2765 + if (!container) { 2766 + throw new Error("Map container not found."); 2767 + } else if (container._leaflet_id) { 2768 + throw new Error("Map container is already initialized."); 2769 + } 2770 + on(container, "scroll", this._onScroll, this); 2771 + this._containerId = stamp(container); 2772 + }, 2773 + _initLayout: function() { 2774 + var container = this._container; 2775 + this._fadeAnimated = this.options.fadeAnimation && Browser.any3d; 2776 + addClass(container, "leaflet-container" + (Browser.touch ? " leaflet-touch" : "") + (Browser.retina ? " leaflet-retina" : "") + (Browser.ielt9 ? " leaflet-oldie" : "") + (Browser.safari ? " leaflet-safari" : "") + (this._fadeAnimated ? " leaflet-fade-anim" : "")); 2777 + var position = getStyle(container, "position"); 2778 + if (position !== "absolute" && position !== "relative" && position !== "fixed" && position !== "sticky") { 2779 + container.style.position = "relative"; 2780 + } 2781 + this._initPanes(); 2782 + if (this._initControlPos) { 2783 + this._initControlPos(); 2784 + } 2785 + }, 2786 + _initPanes: function() { 2787 + var panes = this._panes = {}; 2788 + this._paneRenderers = {}; 2789 + this._mapPane = this.createPane("mapPane", this._container); 2790 + setPosition(this._mapPane, new Point(0, 0)); 2791 + this.createPane("tilePane"); 2792 + this.createPane("overlayPane"); 2793 + this.createPane("shadowPane"); 2794 + this.createPane("markerPane"); 2795 + this.createPane("tooltipPane"); 2796 + this.createPane("popupPane"); 2797 + if (!this.options.markerZoomAnimation) { 2798 + addClass(panes.markerPane, "leaflet-zoom-hide"); 2799 + addClass(panes.shadowPane, "leaflet-zoom-hide"); 2800 + } 2801 + }, 2802 + // private methods that modify map state 2803 + // @section Map state change events 2804 + _resetView: function(center, zoom2, noMoveStart) { 2805 + setPosition(this._mapPane, new Point(0, 0)); 2806 + var loading = !this._loaded; 2807 + this._loaded = true; 2808 + zoom2 = this._limitZoom(zoom2); 2809 + this.fire("viewprereset"); 2810 + var zoomChanged = this._zoom !== zoom2; 2811 + this._moveStart(zoomChanged, noMoveStart)._move(center, zoom2)._moveEnd(zoomChanged); 2812 + this.fire("viewreset"); 2813 + if (loading) { 2814 + this.fire("load"); 2815 + } 2816 + }, 2817 + _moveStart: function(zoomChanged, noMoveStart) { 2818 + if (zoomChanged) { 2819 + this.fire("zoomstart"); 2820 + } 2821 + if (!noMoveStart) { 2822 + this.fire("movestart"); 2823 + } 2824 + return this; 2825 + }, 2826 + _move: function(center, zoom2, data, supressEvent) { 2827 + if (zoom2 === void 0) { 2828 + zoom2 = this._zoom; 2829 + } 2830 + var zoomChanged = this._zoom !== zoom2; 2831 + this._zoom = zoom2; 2832 + this._lastCenter = center; 2833 + this._pixelOrigin = this._getNewPixelOrigin(center); 2834 + if (!supressEvent) { 2835 + if (zoomChanged || data && data.pinch) { 2836 + this.fire("zoom", data); 2837 + } 2838 + this.fire("move", data); 2839 + } else if (data && data.pinch) { 2840 + this.fire("zoom", data); 2841 + } 2842 + return this; 2843 + }, 2844 + _moveEnd: function(zoomChanged) { 2845 + if (zoomChanged) { 2846 + this.fire("zoomend"); 2847 + } 2848 + return this.fire("moveend"); 2849 + }, 2850 + _stop: function() { 2851 + cancelAnimFrame(this._flyToFrame); 2852 + if (this._panAnim) { 2853 + this._panAnim.stop(); 2854 + } 2855 + return this; 2856 + }, 2857 + _rawPanBy: function(offset) { 2858 + setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); 2859 + }, 2860 + _getZoomSpan: function() { 2861 + return this.getMaxZoom() - this.getMinZoom(); 2862 + }, 2863 + _panInsideMaxBounds: function() { 2864 + if (!this._enforcingBounds) { 2865 + this.panInsideBounds(this.options.maxBounds); 2866 + } 2867 + }, 2868 + _checkIfLoaded: function() { 2869 + if (!this._loaded) { 2870 + throw new Error("Set map center and zoom first."); 2871 + } 2872 + }, 2873 + // DOM event handling 2874 + // @section Interaction events 2875 + _initEvents: function(remove2) { 2876 + this._targets = {}; 2877 + this._targets[stamp(this._container)] = this; 2878 + var onOff = remove2 ? off : on; 2879 + onOff(this._container, "click dblclick mousedown mouseup mouseover mouseout mousemove contextmenu keypress keydown keyup", this._handleDOMEvent, this); 2880 + if (this.options.trackResize) { 2881 + onOff(window, "resize", this._onResize, this); 2882 + } 2883 + if (Browser.any3d && this.options.transform3DLimit) { 2884 + (remove2 ? this.off : this.on).call(this, "moveend", this._onMoveEnd); 2885 + } 2886 + }, 2887 + _onResize: function() { 2888 + cancelAnimFrame(this._resizeRequest); 2889 + this._resizeRequest = requestAnimFrame( 2890 + function() { 2891 + this.invalidateSize({ debounceMoveend: true }); 2892 + }, 2893 + this 2894 + ); 2895 + }, 2896 + _onScroll: function() { 2897 + this._container.scrollTop = 0; 2898 + this._container.scrollLeft = 0; 2899 + }, 2900 + _onMoveEnd: function() { 2901 + var pos = this._getMapPanePos(); 2902 + if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) { 2903 + this._resetView(this.getCenter(), this.getZoom()); 2904 + } 2905 + }, 2906 + _findEventTargets: function(e, type) { 2907 + var targets = [], target, isHover = type === "mouseout" || type === "mouseover", src = e.target || e.srcElement, dragging = false; 2908 + while (src) { 2909 + target = this._targets[stamp(src)]; 2910 + if (target && (type === "click" || type === "preclick") && this._draggableMoved(target)) { 2911 + dragging = true; 2912 + break; 2913 + } 2914 + if (target && target.listens(type, true)) { 2915 + if (isHover && !isExternalTarget(src, e)) { 2916 + break; 2917 + } 2918 + targets.push(target); 2919 + if (isHover) { 2920 + break; 2921 + } 2922 + } 2923 + if (src === this._container) { 2924 + break; 2925 + } 2926 + src = src.parentNode; 2927 + } 2928 + if (!targets.length && !dragging && !isHover && this.listens(type, true)) { 2929 + targets = [this]; 2930 + } 2931 + return targets; 2932 + }, 2933 + _isClickDisabled: function(el) { 2934 + while (el && el !== this._container) { 2935 + if (el["_leaflet_disable_click"]) { 2936 + return true; 2937 + } 2938 + el = el.parentNode; 2939 + } 2940 + }, 2941 + _handleDOMEvent: function(e) { 2942 + var el = e.target || e.srcElement; 2943 + if (!this._loaded || el["_leaflet_disable_events"] || e.type === "click" && this._isClickDisabled(el)) { 2944 + return; 2945 + } 2946 + var type = e.type; 2947 + if (type === "mousedown") { 2948 + preventOutline(el); 2949 + } 2950 + this._fireDOMEvent(e, type); 2951 + }, 2952 + _mouseEvents: ["click", "dblclick", "mouseover", "mouseout", "contextmenu"], 2953 + _fireDOMEvent: function(e, type, canvasTargets) { 2954 + if (e.type === "click") { 2955 + var synth = extend({}, e); 2956 + synth.type = "preclick"; 2957 + this._fireDOMEvent(synth, synth.type, canvasTargets); 2958 + } 2959 + var targets = this._findEventTargets(e, type); 2960 + if (canvasTargets) { 2961 + var filtered = []; 2962 + for (var i = 0; i < canvasTargets.length; i++) { 2963 + if (canvasTargets[i].listens(type, true)) { 2964 + filtered.push(canvasTargets[i]); 2965 + } 2966 + } 2967 + targets = filtered.concat(targets); 2968 + } 2969 + if (!targets.length) { 2970 + return; 2971 + } 2972 + if (type === "contextmenu") { 2973 + preventDefault(e); 2974 + } 2975 + var target = targets[0]; 2976 + var data = { 2977 + originalEvent: e 2978 + }; 2979 + if (e.type !== "keypress" && e.type !== "keydown" && e.type !== "keyup") { 2980 + var isMarker = target.getLatLng && (!target._radius || target._radius <= 10); 2981 + data.containerPoint = isMarker ? this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e); 2982 + data.layerPoint = this.containerPointToLayerPoint(data.containerPoint); 2983 + data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint); 2984 + } 2985 + for (i = 0; i < targets.length; i++) { 2986 + targets[i].fire(type, data, true); 2987 + if (data.originalEvent._stopped || targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1) { 2988 + return; 2989 + } 2990 + } 2991 + }, 2992 + _draggableMoved: function(obj) { 2993 + obj = obj.dragging && obj.dragging.enabled() ? obj : this; 2994 + return obj.dragging && obj.dragging.moved() || this.boxZoom && this.boxZoom.moved(); 2995 + }, 2996 + _clearHandlers: function() { 2997 + for (var i = 0, len = this._handlers.length; i < len; i++) { 2998 + this._handlers[i].disable(); 2999 + } 3000 + }, 3001 + // @section Other Methods 3002 + // @method whenReady(fn: Function, context?: Object): this 3003 + // Runs the given function `fn` when the map gets initialized with 3004 + // a view (center and zoom) and at least one layer, or immediately 3005 + // if it's already initialized, optionally passing a function context. 3006 + whenReady: function(callback, context) { 3007 + if (this._loaded) { 3008 + callback.call(context || this, { target: this }); 3009 + } else { 3010 + this.on("load", callback, context); 3011 + } 3012 + return this; 3013 + }, 3014 + // private methods for getting map state 3015 + _getMapPanePos: function() { 3016 + return getPosition(this._mapPane) || new Point(0, 0); 3017 + }, 3018 + _moved: function() { 3019 + var pos = this._getMapPanePos(); 3020 + return pos && !pos.equals([0, 0]); 3021 + }, 3022 + _getTopLeftPoint: function(center, zoom2) { 3023 + var pixelOrigin = center && zoom2 !== void 0 ? this._getNewPixelOrigin(center, zoom2) : this.getPixelOrigin(); 3024 + return pixelOrigin.subtract(this._getMapPanePos()); 3025 + }, 3026 + _getNewPixelOrigin: function(center, zoom2) { 3027 + var viewHalf = this.getSize()._divideBy(2); 3028 + return this.project(center, zoom2)._subtract(viewHalf)._add(this._getMapPanePos())._round(); 3029 + }, 3030 + _latLngToNewLayerPoint: function(latlng, zoom2, center) { 3031 + var topLeft = this._getNewPixelOrigin(center, zoom2); 3032 + return this.project(latlng, zoom2)._subtract(topLeft); 3033 + }, 3034 + _latLngBoundsToNewLayerBounds: function(latLngBounds, zoom2, center) { 3035 + var topLeft = this._getNewPixelOrigin(center, zoom2); 3036 + return toBounds([ 3037 + this.project(latLngBounds.getSouthWest(), zoom2)._subtract(topLeft), 3038 + this.project(latLngBounds.getNorthWest(), zoom2)._subtract(topLeft), 3039 + this.project(latLngBounds.getSouthEast(), zoom2)._subtract(topLeft), 3040 + this.project(latLngBounds.getNorthEast(), zoom2)._subtract(topLeft) 3041 + ]); 3042 + }, 3043 + // layer point of the current center 3044 + _getCenterLayerPoint: function() { 3045 + return this.containerPointToLayerPoint(this.getSize()._divideBy(2)); 3046 + }, 3047 + // offset of the specified place to the current center in pixels 3048 + _getCenterOffset: function(latlng) { 3049 + return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); 3050 + }, 3051 + // adjust center for view to get inside bounds 3052 + _limitCenter: function(center, zoom2, bounds) { 3053 + if (!bounds) { 3054 + return center; 3055 + } 3056 + var centerPoint = this.project(center, zoom2), viewHalf = this.getSize().divideBy(2), viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), offset = this._getBoundsOffset(viewBounds, bounds, zoom2); 3057 + if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) { 3058 + return center; 3059 + } 3060 + return this.unproject(centerPoint.add(offset), zoom2); 3061 + }, 3062 + // adjust offset for view to get inside bounds 3063 + _limitOffset: function(offset, bounds) { 3064 + if (!bounds) { 3065 + return offset; 3066 + } 3067 + var viewBounds = this.getPixelBounds(), newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); 3068 + return offset.add(this._getBoundsOffset(newBounds, bounds)); 3069 + }, 3070 + // returns offset needed for pxBounds to get inside maxBounds at a specified zoom 3071 + _getBoundsOffset: function(pxBounds, maxBounds, zoom2) { 3072 + var projectedMaxBounds = toBounds( 3073 + this.project(maxBounds.getNorthEast(), zoom2), 3074 + this.project(maxBounds.getSouthWest(), zoom2) 3075 + ), minOffset = projectedMaxBounds.min.subtract(pxBounds.min), maxOffset = projectedMaxBounds.max.subtract(pxBounds.max), dx = this._rebound(minOffset.x, -maxOffset.x), dy = this._rebound(minOffset.y, -maxOffset.y); 3076 + return new Point(dx, dy); 3077 + }, 3078 + _rebound: function(left, right) { 3079 + return left + right > 0 ? Math.round(left - right) / 2 : Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); 3080 + }, 3081 + _limitZoom: function(zoom2) { 3082 + var min = this.getMinZoom(), max = this.getMaxZoom(), snap = Browser.any3d ? this.options.zoomSnap : 1; 3083 + if (snap) { 3084 + zoom2 = Math.round(zoom2 / snap) * snap; 3085 + } 3086 + return Math.max(min, Math.min(max, zoom2)); 3087 + }, 3088 + _onPanTransitionStep: function() { 3089 + this.fire("move"); 3090 + }, 3091 + _onPanTransitionEnd: function() { 3092 + removeClass(this._mapPane, "leaflet-pan-anim"); 3093 + this.fire("moveend"); 3094 + }, 3095 + _tryAnimatedPan: function(center, options) { 3096 + var offset = this._getCenterOffset(center)._trunc(); 3097 + if ((options && options.animate) !== true && !this.getSize().contains(offset)) { 3098 + return false; 3099 + } 3100 + this.panBy(offset, options); 3101 + return true; 3102 + }, 3103 + _createAnimProxy: function() { 3104 + var proxy = this._proxy = create$1("div", "leaflet-proxy leaflet-zoom-animated"); 3105 + this._panes.mapPane.appendChild(proxy); 3106 + this.on("zoomanim", function(e) { 3107 + var prop = TRANSFORM, transform = this._proxy.style[prop]; 3108 + setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1)); 3109 + if (transform === this._proxy.style[prop] && this._animatingZoom) { 3110 + this._onZoomTransitionEnd(); 3111 + } 3112 + }, this); 3113 + this.on("load moveend", this._animMoveEnd, this); 3114 + this._on("unload", this._destroyAnimProxy, this); 3115 + }, 3116 + _destroyAnimProxy: function() { 3117 + remove(this._proxy); 3118 + this.off("load moveend", this._animMoveEnd, this); 3119 + delete this._proxy; 3120 + }, 3121 + _animMoveEnd: function() { 3122 + var c = this.getCenter(), z = this.getZoom(); 3123 + setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1)); 3124 + }, 3125 + _catchTransitionEnd: function(e) { 3126 + if (this._animatingZoom && e.propertyName.indexOf("transform") >= 0) { 3127 + this._onZoomTransitionEnd(); 3128 + } 3129 + }, 3130 + _nothingToAnimate: function() { 3131 + return !this._container.getElementsByClassName("leaflet-zoom-animated").length; 3132 + }, 3133 + _tryAnimatedZoom: function(center, zoom2, options) { 3134 + if (this._animatingZoom) { 3135 + return true; 3136 + } 3137 + options = options || {}; 3138 + if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() || Math.abs(zoom2 - this._zoom) > this.options.zoomAnimationThreshold) { 3139 + return false; 3140 + } 3141 + var scale2 = this.getZoomScale(zoom2), offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale2); 3142 + if (options.animate !== true && !this.getSize().contains(offset)) { 3143 + return false; 3144 + } 3145 + requestAnimFrame(function() { 3146 + this._moveStart(true, options.noMoveStart || false)._animateZoom(center, zoom2, true); 3147 + }, this); 3148 + return true; 3149 + }, 3150 + _animateZoom: function(center, zoom2, startAnim, noUpdate) { 3151 + if (!this._mapPane) { 3152 + return; 3153 + } 3154 + if (startAnim) { 3155 + this._animatingZoom = true; 3156 + this._animateToCenter = center; 3157 + this._animateToZoom = zoom2; 3158 + addClass(this._mapPane, "leaflet-zoom-anim"); 3159 + } 3160 + this.fire("zoomanim", { 3161 + center, 3162 + zoom: zoom2, 3163 + noUpdate 3164 + }); 3165 + if (!this._tempFireZoomEvent) { 3166 + this._tempFireZoomEvent = this._zoom !== this._animateToZoom; 3167 + } 3168 + this._move(this._animateToCenter, this._animateToZoom, void 0, true); 3169 + setTimeout(bind(this._onZoomTransitionEnd, this), 250); 3170 + }, 3171 + _onZoomTransitionEnd: function() { 3172 + if (!this._animatingZoom) { 3173 + return; 3174 + } 3175 + if (this._mapPane) { 3176 + removeClass(this._mapPane, "leaflet-zoom-anim"); 3177 + } 3178 + this._animatingZoom = false; 3179 + this._move(this._animateToCenter, this._animateToZoom, void 0, true); 3180 + if (this._tempFireZoomEvent) { 3181 + this.fire("zoom"); 3182 + } 3183 + delete this._tempFireZoomEvent; 3184 + this.fire("move"); 3185 + this._moveEnd(true); 3186 + } 3187 + }); 3188 + function createMap2(id, options) { 3189 + return new Map2(id, options); 3190 + } 3191 + var Control = Class.extend({ 3192 + // @section 3193 + // @aka Control Options 3194 + options: { 3195 + // @option position: String = 'topright' 3196 + // The position of the control (one of the map corners). Possible values are `'topleft'`, 3197 + // `'topright'`, `'bottomleft'` or `'bottomright'` 3198 + position: "topright" 3199 + }, 3200 + initialize: function(options) { 3201 + setOptions(this, options); 3202 + }, 3203 + /* @section 3204 + * Classes extending L.Control will inherit the following methods: 3205 + * 3206 + * @method getPosition: string 3207 + * Returns the position of the control. 3208 + */ 3209 + getPosition: function() { 3210 + return this.options.position; 3211 + }, 3212 + // @method setPosition(position: string): this 3213 + // Sets the position of the control. 3214 + setPosition: function(position) { 3215 + var map2 = this._map; 3216 + if (map2) { 3217 + map2.removeControl(this); 3218 + } 3219 + this.options.position = position; 3220 + if (map2) { 3221 + map2.addControl(this); 3222 + } 3223 + return this; 3224 + }, 3225 + // @method getContainer: HTMLElement 3226 + // Returns the HTMLElement that contains the control. 3227 + getContainer: function() { 3228 + return this._container; 3229 + }, 3230 + // @method addTo(map: Map): this 3231 + // Adds the control to the given map. 3232 + addTo: function(map2) { 3233 + this.remove(); 3234 + this._map = map2; 3235 + var container = this._container = this.onAdd(map2), pos = this.getPosition(), corner = map2._controlCorners[pos]; 3236 + addClass(container, "leaflet-control"); 3237 + if (pos.indexOf("bottom") !== -1) { 3238 + corner.insertBefore(container, corner.firstChild); 3239 + } else { 3240 + corner.appendChild(container); 3241 + } 3242 + this._map.on("unload", this.remove, this); 3243 + return this; 3244 + }, 3245 + // @method remove: this 3246 + // Removes the control from the map it is currently active on. 3247 + remove: function() { 3248 + if (!this._map) { 3249 + return this; 3250 + } 3251 + remove(this._container); 3252 + if (this.onRemove) { 3253 + this.onRemove(this._map); 3254 + } 3255 + this._map.off("unload", this.remove, this); 3256 + this._map = null; 3257 + return this; 3258 + }, 3259 + _refocusOnMap: function(e) { 3260 + if (this._map && e && e.screenX > 0 && e.screenY > 0) { 3261 + this._map.getContainer().focus(); 3262 + } 3263 + } 3264 + }); 3265 + var control = function(options) { 3266 + return new Control(options); 3267 + }; 3268 + Map2.include({ 3269 + // @method addControl(control: Control): this 3270 + // Adds the given control to the map 3271 + addControl: function(control2) { 3272 + control2.addTo(this); 3273 + return this; 3274 + }, 3275 + // @method removeControl(control: Control): this 3276 + // Removes the given control from the map 3277 + removeControl: function(control2) { 3278 + control2.remove(); 3279 + return this; 3280 + }, 3281 + _initControlPos: function() { 3282 + var corners = this._controlCorners = {}, l = "leaflet-", container = this._controlContainer = create$1("div", l + "control-container", this._container); 3283 + function createCorner(vSide, hSide) { 3284 + var className = l + vSide + " " + l + hSide; 3285 + corners[vSide + hSide] = create$1("div", className, container); 3286 + } 3287 + createCorner("top", "left"); 3288 + createCorner("top", "right"); 3289 + createCorner("bottom", "left"); 3290 + createCorner("bottom", "right"); 3291 + }, 3292 + _clearControlPos: function() { 3293 + for (var i in this._controlCorners) { 3294 + remove(this._controlCorners[i]); 3295 + } 3296 + remove(this._controlContainer); 3297 + delete this._controlCorners; 3298 + delete this._controlContainer; 3299 + } 3300 + }); 3301 + var Layers = Control.extend({ 3302 + // @section 3303 + // @aka Control.Layers options 3304 + options: { 3305 + // @option collapsed: Boolean = true 3306 + // If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation. 3307 + collapsed: true, 3308 + position: "topright", 3309 + // @option autoZIndex: Boolean = true 3310 + // If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off. 3311 + autoZIndex: true, 3312 + // @option hideSingleBase: Boolean = false 3313 + // If `true`, the base layers in the control will be hidden when there is only one. 3314 + hideSingleBase: false, 3315 + // @option sortLayers: Boolean = false 3316 + // Whether to sort the layers. When `false`, layers will keep the order 3317 + // in which they were added to the control. 3318 + sortLayers: false, 3319 + // @option sortFunction: Function = * 3320 + // A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 3321 + // that will be used for sorting the layers, when `sortLayers` is `true`. 3322 + // The function receives both the `L.Layer` instances and their names, as in 3323 + // `sortFunction(layerA, layerB, nameA, nameB)`. 3324 + // By default, it sorts layers alphabetically by their name. 3325 + sortFunction: function(layerA, layerB, nameA, nameB) { 3326 + return nameA < nameB ? -1 : nameB < nameA ? 1 : 0; 3327 + } 3328 + }, 3329 + initialize: function(baseLayers, overlays, options) { 3330 + setOptions(this, options); 3331 + this._layerControlInputs = []; 3332 + this._layers = []; 3333 + this._lastZIndex = 0; 3334 + this._handlingClick = false; 3335 + this._preventClick = false; 3336 + for (var i in baseLayers) { 3337 + this._addLayer(baseLayers[i], i); 3338 + } 3339 + for (i in overlays) { 3340 + this._addLayer(overlays[i], i, true); 3341 + } 3342 + }, 3343 + onAdd: function(map2) { 3344 + this._initLayout(); 3345 + this._update(); 3346 + this._map = map2; 3347 + map2.on("zoomend", this._checkDisabledLayers, this); 3348 + for (var i = 0; i < this._layers.length; i++) { 3349 + this._layers[i].layer.on("add remove", this._onLayerChange, this); 3350 + } 3351 + return this._container; 3352 + }, 3353 + addTo: function(map2) { 3354 + Control.prototype.addTo.call(this, map2); 3355 + return this._expandIfNotCollapsed(); 3356 + }, 3357 + onRemove: function() { 3358 + this._map.off("zoomend", this._checkDisabledLayers, this); 3359 + for (var i = 0; i < this._layers.length; i++) { 3360 + this._layers[i].layer.off("add remove", this._onLayerChange, this); 3361 + } 3362 + }, 3363 + // @method addBaseLayer(layer: Layer, name: String): this 3364 + // Adds a base layer (radio button entry) with the given name to the control. 3365 + addBaseLayer: function(layer, name) { 3366 + this._addLayer(layer, name); 3367 + return this._map ? this._update() : this; 3368 + }, 3369 + // @method addOverlay(layer: Layer, name: String): this 3370 + // Adds an overlay (checkbox entry) with the given name to the control. 3371 + addOverlay: function(layer, name) { 3372 + this._addLayer(layer, name, true); 3373 + return this._map ? this._update() : this; 3374 + }, 3375 + // @method removeLayer(layer: Layer): this 3376 + // Remove the given layer from the control. 3377 + removeLayer: function(layer) { 3378 + layer.off("add remove", this._onLayerChange, this); 3379 + var obj = this._getLayer(stamp(layer)); 3380 + if (obj) { 3381 + this._layers.splice(this._layers.indexOf(obj), 1); 3382 + } 3383 + return this._map ? this._update() : this; 3384 + }, 3385 + // @method expand(): this 3386 + // Expand the control container if collapsed. 3387 + expand: function() { 3388 + addClass(this._container, "leaflet-control-layers-expanded"); 3389 + this._section.style.height = null; 3390 + var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50); 3391 + if (acceptableHeight < this._section.clientHeight) { 3392 + addClass(this._section, "leaflet-control-layers-scrollbar"); 3393 + this._section.style.height = acceptableHeight + "px"; 3394 + } else { 3395 + removeClass(this._section, "leaflet-control-layers-scrollbar"); 3396 + } 3397 + this._checkDisabledLayers(); 3398 + return this; 3399 + }, 3400 + // @method collapse(): this 3401 + // Collapse the control container if expanded. 3402 + collapse: function() { 3403 + removeClass(this._container, "leaflet-control-layers-expanded"); 3404 + return this; 3405 + }, 3406 + _initLayout: function() { 3407 + var className = "leaflet-control-layers", container = this._container = create$1("div", className), collapsed = this.options.collapsed; 3408 + container.setAttribute("aria-haspopup", true); 3409 + disableClickPropagation(container); 3410 + disableScrollPropagation(container); 3411 + var section = this._section = create$1("section", className + "-list"); 3412 + if (collapsed) { 3413 + this._map.on("click", this.collapse, this); 3414 + on(container, { 3415 + mouseenter: this._expandSafely, 3416 + mouseleave: this.collapse 3417 + }, this); 3418 + } 3419 + var link = this._layersLink = create$1("a", className + "-toggle", container); 3420 + link.href = "#"; 3421 + link.title = "Layers"; 3422 + link.setAttribute("role", "button"); 3423 + on(link, { 3424 + keydown: function(e) { 3425 + if (e.keyCode === 13) { 3426 + this._expandSafely(); 3427 + } 3428 + }, 3429 + // Certain screen readers intercept the key event and instead send a click event 3430 + click: function(e) { 3431 + preventDefault(e); 3432 + this._expandSafely(); 3433 + } 3434 + }, this); 3435 + if (!collapsed) { 3436 + this.expand(); 3437 + } 3438 + this._baseLayersList = create$1("div", className + "-base", section); 3439 + this._separator = create$1("div", className + "-separator", section); 3440 + this._overlaysList = create$1("div", className + "-overlays", section); 3441 + container.appendChild(section); 3442 + }, 3443 + _getLayer: function(id) { 3444 + for (var i = 0; i < this._layers.length; i++) { 3445 + if (this._layers[i] && stamp(this._layers[i].layer) === id) { 3446 + return this._layers[i]; 3447 + } 3448 + } 3449 + }, 3450 + _addLayer: function(layer, name, overlay) { 3451 + if (this._map) { 3452 + layer.on("add remove", this._onLayerChange, this); 3453 + } 3454 + this._layers.push({ 3455 + layer, 3456 + name, 3457 + overlay 3458 + }); 3459 + if (this.options.sortLayers) { 3460 + this._layers.sort(bind(function(a, b) { 3461 + return this.options.sortFunction(a.layer, b.layer, a.name, b.name); 3462 + }, this)); 3463 + } 3464 + if (this.options.autoZIndex && layer.setZIndex) { 3465 + this._lastZIndex++; 3466 + layer.setZIndex(this._lastZIndex); 3467 + } 3468 + this._expandIfNotCollapsed(); 3469 + }, 3470 + _update: function() { 3471 + if (!this._container) { 3472 + return this; 3473 + } 3474 + empty(this._baseLayersList); 3475 + empty(this._overlaysList); 3476 + this._layerControlInputs = []; 3477 + var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0; 3478 + for (i = 0; i < this._layers.length; i++) { 3479 + obj = this._layers[i]; 3480 + this._addItem(obj); 3481 + overlaysPresent = overlaysPresent || obj.overlay; 3482 + baseLayersPresent = baseLayersPresent || !obj.overlay; 3483 + baseLayersCount += !obj.overlay ? 1 : 0; 3484 + } 3485 + if (this.options.hideSingleBase) { 3486 + baseLayersPresent = baseLayersPresent && baseLayersCount > 1; 3487 + this._baseLayersList.style.display = baseLayersPresent ? "" : "none"; 3488 + } 3489 + this._separator.style.display = overlaysPresent && baseLayersPresent ? "" : "none"; 3490 + return this; 3491 + }, 3492 + _onLayerChange: function(e) { 3493 + if (!this._handlingClick) { 3494 + this._update(); 3495 + } 3496 + var obj = this._getLayer(stamp(e.target)); 3497 + var type = obj.overlay ? e.type === "add" ? "overlayadd" : "overlayremove" : e.type === "add" ? "baselayerchange" : null; 3498 + if (type) { 3499 + this._map.fire(type, obj); 3500 + } 3501 + }, 3502 + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079) 3503 + _createRadioElement: function(name, checked) { 3504 + var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' + name + '"' + (checked ? ' checked="checked"' : "") + "/>"; 3505 + var radioFragment = document.createElement("div"); 3506 + radioFragment.innerHTML = radioHtml; 3507 + return radioFragment.firstChild; 3508 + }, 3509 + _addItem: function(obj) { 3510 + var label = document.createElement("label"), checked = this._map.hasLayer(obj.layer), input; 3511 + if (obj.overlay) { 3512 + input = document.createElement("input"); 3513 + input.type = "checkbox"; 3514 + input.className = "leaflet-control-layers-selector"; 3515 + input.defaultChecked = checked; 3516 + } else { 3517 + input = this._createRadioElement("leaflet-base-layers_" + stamp(this), checked); 3518 + } 3519 + this._layerControlInputs.push(input); 3520 + input.layerId = stamp(obj.layer); 3521 + on(input, "click", this._onInputClick, this); 3522 + var name = document.createElement("span"); 3523 + name.innerHTML = " " + obj.name; 3524 + var holder = document.createElement("span"); 3525 + label.appendChild(holder); 3526 + holder.appendChild(input); 3527 + holder.appendChild(name); 3528 + var container = obj.overlay ? this._overlaysList : this._baseLayersList; 3529 + container.appendChild(label); 3530 + this._checkDisabledLayers(); 3531 + return label; 3532 + }, 3533 + _onInputClick: function() { 3534 + if (this._preventClick) { 3535 + return; 3536 + } 3537 + var inputs = this._layerControlInputs, input, layer; 3538 + var addedLayers = [], removedLayers = []; 3539 + this._handlingClick = true; 3540 + for (var i = inputs.length - 1; i >= 0; i--) { 3541 + input = inputs[i]; 3542 + layer = this._getLayer(input.layerId).layer; 3543 + if (input.checked) { 3544 + addedLayers.push(layer); 3545 + } else if (!input.checked) { 3546 + removedLayers.push(layer); 3547 + } 3548 + } 3549 + for (i = 0; i < removedLayers.length; i++) { 3550 + if (this._map.hasLayer(removedLayers[i])) { 3551 + this._map.removeLayer(removedLayers[i]); 3552 + } 3553 + } 3554 + for (i = 0; i < addedLayers.length; i++) { 3555 + if (!this._map.hasLayer(addedLayers[i])) { 3556 + this._map.addLayer(addedLayers[i]); 3557 + } 3558 + } 3559 + this._handlingClick = false; 3560 + this._refocusOnMap(); 3561 + }, 3562 + _checkDisabledLayers: function() { 3563 + var inputs = this._layerControlInputs, input, layer, zoom2 = this._map.getZoom(); 3564 + for (var i = inputs.length - 1; i >= 0; i--) { 3565 + input = inputs[i]; 3566 + layer = this._getLayer(input.layerId).layer; 3567 + input.disabled = layer.options.minZoom !== void 0 && zoom2 < layer.options.minZoom || layer.options.maxZoom !== void 0 && zoom2 > layer.options.maxZoom; 3568 + } 3569 + }, 3570 + _expandIfNotCollapsed: function() { 3571 + if (this._map && !this.options.collapsed) { 3572 + this.expand(); 3573 + } 3574 + return this; 3575 + }, 3576 + _expandSafely: function() { 3577 + var section = this._section; 3578 + this._preventClick = true; 3579 + on(section, "click", preventDefault); 3580 + this.expand(); 3581 + var that = this; 3582 + setTimeout(function() { 3583 + off(section, "click", preventDefault); 3584 + that._preventClick = false; 3585 + }); 3586 + } 3587 + }); 3588 + var layers = function(baseLayers, overlays, options) { 3589 + return new Layers(baseLayers, overlays, options); 3590 + }; 3591 + var Zoom = Control.extend({ 3592 + // @section 3593 + // @aka Control.Zoom options 3594 + options: { 3595 + position: "topleft", 3596 + // @option zoomInText: String = '<span aria-hidden="true">+</span>' 3597 + // The text set on the 'zoom in' button. 3598 + zoomInText: '<span aria-hidden="true">+</span>', 3599 + // @option zoomInTitle: String = 'Zoom in' 3600 + // The title set on the 'zoom in' button. 3601 + zoomInTitle: "Zoom in", 3602 + // @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>' 3603 + // The text set on the 'zoom out' button. 3604 + zoomOutText: '<span aria-hidden="true">&#x2212;</span>', 3605 + // @option zoomOutTitle: String = 'Zoom out' 3606 + // The title set on the 'zoom out' button. 3607 + zoomOutTitle: "Zoom out" 3608 + }, 3609 + onAdd: function(map2) { 3610 + var zoomName = "leaflet-control-zoom", container = create$1("div", zoomName + " leaflet-bar"), options = this.options; 3611 + this._zoomInButton = this._createButton( 3612 + options.zoomInText, 3613 + options.zoomInTitle, 3614 + zoomName + "-in", 3615 + container, 3616 + this._zoomIn 3617 + ); 3618 + this._zoomOutButton = this._createButton( 3619 + options.zoomOutText, 3620 + options.zoomOutTitle, 3621 + zoomName + "-out", 3622 + container, 3623 + this._zoomOut 3624 + ); 3625 + this._updateDisabled(); 3626 + map2.on("zoomend zoomlevelschange", this._updateDisabled, this); 3627 + return container; 3628 + }, 3629 + onRemove: function(map2) { 3630 + map2.off("zoomend zoomlevelschange", this._updateDisabled, this); 3631 + }, 3632 + disable: function() { 3633 + this._disabled = true; 3634 + this._updateDisabled(); 3635 + return this; 3636 + }, 3637 + enable: function() { 3638 + this._disabled = false; 3639 + this._updateDisabled(); 3640 + return this; 3641 + }, 3642 + _zoomIn: function(e) { 3643 + if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) { 3644 + this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 3645 + } 3646 + }, 3647 + _zoomOut: function(e) { 3648 + if (!this._disabled && this._map._zoom > this._map.getMinZoom()) { 3649 + this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1)); 3650 + } 3651 + }, 3652 + _createButton: function(html, title, className, container, fn) { 3653 + var link = create$1("a", className, container); 3654 + link.innerHTML = html; 3655 + link.href = "#"; 3656 + link.title = title; 3657 + link.setAttribute("role", "button"); 3658 + link.setAttribute("aria-label", title); 3659 + disableClickPropagation(link); 3660 + on(link, "click", stop); 3661 + on(link, "click", fn, this); 3662 + on(link, "click", this._refocusOnMap, this); 3663 + return link; 3664 + }, 3665 + _updateDisabled: function() { 3666 + var map2 = this._map, className = "leaflet-disabled"; 3667 + removeClass(this._zoomInButton, className); 3668 + removeClass(this._zoomOutButton, className); 3669 + this._zoomInButton.setAttribute("aria-disabled", "false"); 3670 + this._zoomOutButton.setAttribute("aria-disabled", "false"); 3671 + if (this._disabled || map2._zoom === map2.getMinZoom()) { 3672 + addClass(this._zoomOutButton, className); 3673 + this._zoomOutButton.setAttribute("aria-disabled", "true"); 3674 + } 3675 + if (this._disabled || map2._zoom === map2.getMaxZoom()) { 3676 + addClass(this._zoomInButton, className); 3677 + this._zoomInButton.setAttribute("aria-disabled", "true"); 3678 + } 3679 + } 3680 + }); 3681 + Map2.mergeOptions({ 3682 + zoomControl: true 3683 + }); 3684 + Map2.addInitHook(function() { 3685 + if (this.options.zoomControl) { 3686 + this.zoomControl = new Zoom(); 3687 + this.addControl(this.zoomControl); 3688 + } 3689 + }); 3690 + var zoom = function(options) { 3691 + return new Zoom(options); 3692 + }; 3693 + var Scale = Control.extend({ 3694 + // @section 3695 + // @aka Control.Scale options 3696 + options: { 3697 + position: "bottomleft", 3698 + // @option maxWidth: Number = 100 3699 + // Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500). 3700 + maxWidth: 100, 3701 + // @option metric: Boolean = True 3702 + // Whether to show the metric scale line (m/km). 3703 + metric: true, 3704 + // @option imperial: Boolean = True 3705 + // Whether to show the imperial scale line (mi/ft). 3706 + imperial: true 3707 + // @option updateWhenIdle: Boolean = false 3708 + // If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)). 3709 + }, 3710 + onAdd: function(map2) { 3711 + var className = "leaflet-control-scale", container = create$1("div", className), options = this.options; 3712 + this._addScales(options, className + "-line", container); 3713 + map2.on(options.updateWhenIdle ? "moveend" : "move", this._update, this); 3714 + map2.whenReady(this._update, this); 3715 + return container; 3716 + }, 3717 + onRemove: function(map2) { 3718 + map2.off(this.options.updateWhenIdle ? "moveend" : "move", this._update, this); 3719 + }, 3720 + _addScales: function(options, className, container) { 3721 + if (options.metric) { 3722 + this._mScale = create$1("div", className, container); 3723 + } 3724 + if (options.imperial) { 3725 + this._iScale = create$1("div", className, container); 3726 + } 3727 + }, 3728 + _update: function() { 3729 + var map2 = this._map, y = map2.getSize().y / 2; 3730 + var maxMeters = map2.distance( 3731 + map2.containerPointToLatLng([0, y]), 3732 + map2.containerPointToLatLng([this.options.maxWidth, y]) 3733 + ); 3734 + this._updateScales(maxMeters); 3735 + }, 3736 + _updateScales: function(maxMeters) { 3737 + if (this.options.metric && maxMeters) { 3738 + this._updateMetric(maxMeters); 3739 + } 3740 + if (this.options.imperial && maxMeters) { 3741 + this._updateImperial(maxMeters); 3742 + } 3743 + }, 3744 + _updateMetric: function(maxMeters) { 3745 + var meters = this._getRoundNum(maxMeters), label = meters < 1e3 ? meters + " m" : meters / 1e3 + " km"; 3746 + this._updateScale(this._mScale, label, meters / maxMeters); 3747 + }, 3748 + _updateImperial: function(maxMeters) { 3749 + var maxFeet = maxMeters * 3.2808399, maxMiles, miles, feet; 3750 + if (maxFeet > 5280) { 3751 + maxMiles = maxFeet / 5280; 3752 + miles = this._getRoundNum(maxMiles); 3753 + this._updateScale(this._iScale, miles + " mi", miles / maxMiles); 3754 + } else { 3755 + feet = this._getRoundNum(maxFeet); 3756 + this._updateScale(this._iScale, feet + " ft", feet / maxFeet); 3757 + } 3758 + }, 3759 + _updateScale: function(scale2, text, ratio) { 3760 + scale2.style.width = Math.round(this.options.maxWidth * ratio) + "px"; 3761 + scale2.innerHTML = text; 3762 + }, 3763 + _getRoundNum: function(num) { 3764 + var pow10 = Math.pow(10, (Math.floor(num) + "").length - 1), d = num / pow10; 3765 + d = d >= 10 ? 10 : d >= 5 ? 5 : d >= 3 ? 3 : d >= 2 ? 2 : 1; 3766 + return pow10 * d; 3767 + } 3768 + }); 3769 + var scale = function(options) { 3770 + return new Scale(options); 3771 + }; 3772 + var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>'; 3773 + var Attribution = Control.extend({ 3774 + // @section 3775 + // @aka Control.Attribution options 3776 + options: { 3777 + position: "bottomright", 3778 + // @option prefix: String|false = 'Leaflet' 3779 + // The HTML text shown before the attributions. Pass `false` to disable. 3780 + prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + " " : "") + "Leaflet</a>" 3781 + }, 3782 + initialize: function(options) { 3783 + setOptions(this, options); 3784 + this._attributions = {}; 3785 + }, 3786 + onAdd: function(map2) { 3787 + map2.attributionControl = this; 3788 + this._container = create$1("div", "leaflet-control-attribution"); 3789 + disableClickPropagation(this._container); 3790 + for (var i in map2._layers) { 3791 + if (map2._layers[i].getAttribution) { 3792 + this.addAttribution(map2._layers[i].getAttribution()); 3793 + } 3794 + } 3795 + this._update(); 3796 + map2.on("layeradd", this._addAttribution, this); 3797 + return this._container; 3798 + }, 3799 + onRemove: function(map2) { 3800 + map2.off("layeradd", this._addAttribution, this); 3801 + }, 3802 + _addAttribution: function(ev) { 3803 + if (ev.layer.getAttribution) { 3804 + this.addAttribution(ev.layer.getAttribution()); 3805 + ev.layer.once("remove", function() { 3806 + this.removeAttribution(ev.layer.getAttribution()); 3807 + }, this); 3808 + } 3809 + }, 3810 + // @method setPrefix(prefix: String|false): this 3811 + // The HTML text shown before the attributions. Pass `false` to disable. 3812 + setPrefix: function(prefix) { 3813 + this.options.prefix = prefix; 3814 + this._update(); 3815 + return this; 3816 + }, 3817 + // @method addAttribution(text: String): this 3818 + // Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`). 3819 + addAttribution: function(text) { 3820 + if (!text) { 3821 + return this; 3822 + } 3823 + if (!this._attributions[text]) { 3824 + this._attributions[text] = 0; 3825 + } 3826 + this._attributions[text]++; 3827 + this._update(); 3828 + return this; 3829 + }, 3830 + // @method removeAttribution(text: String): this 3831 + // Removes an attribution text. 3832 + removeAttribution: function(text) { 3833 + if (!text) { 3834 + return this; 3835 + } 3836 + if (this._attributions[text]) { 3837 + this._attributions[text]--; 3838 + this._update(); 3839 + } 3840 + return this; 3841 + }, 3842 + _update: function() { 3843 + if (!this._map) { 3844 + return; 3845 + } 3846 + var attribs = []; 3847 + for (var i in this._attributions) { 3848 + if (this._attributions[i]) { 3849 + attribs.push(i); 3850 + } 3851 + } 3852 + var prefixAndAttribs = []; 3853 + if (this.options.prefix) { 3854 + prefixAndAttribs.push(this.options.prefix); 3855 + } 3856 + if (attribs.length) { 3857 + prefixAndAttribs.push(attribs.join(", ")); 3858 + } 3859 + this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> '); 3860 + } 3861 + }); 3862 + Map2.mergeOptions({ 3863 + attributionControl: true 3864 + }); 3865 + Map2.addInitHook(function() { 3866 + if (this.options.attributionControl) { 3867 + new Attribution().addTo(this); 3868 + } 3869 + }); 3870 + var attribution = function(options) { 3871 + return new Attribution(options); 3872 + }; 3873 + Control.Layers = Layers; 3874 + Control.Zoom = Zoom; 3875 + Control.Scale = Scale; 3876 + Control.Attribution = Attribution; 3877 + control.layers = layers; 3878 + control.zoom = zoom; 3879 + control.scale = scale; 3880 + control.attribution = attribution; 3881 + var Handler = Class.extend({ 3882 + initialize: function(map2) { 3883 + this._map = map2; 3884 + }, 3885 + // @method enable(): this 3886 + // Enables the handler 3887 + enable: function() { 3888 + if (this._enabled) { 3889 + return this; 3890 + } 3891 + this._enabled = true; 3892 + this.addHooks(); 3893 + return this; 3894 + }, 3895 + // @method disable(): this 3896 + // Disables the handler 3897 + disable: function() { 3898 + if (!this._enabled) { 3899 + return this; 3900 + } 3901 + this._enabled = false; 3902 + this.removeHooks(); 3903 + return this; 3904 + }, 3905 + // @method enabled(): Boolean 3906 + // Returns `true` if the handler is enabled 3907 + enabled: function() { 3908 + return !!this._enabled; 3909 + } 3910 + // @section Extension methods 3911 + // Classes inheriting from `Handler` must implement the two following methods: 3912 + // @method addHooks() 3913 + // Called when the handler is enabled, should add event hooks. 3914 + // @method removeHooks() 3915 + // Called when the handler is disabled, should remove the event hooks added previously. 3916 + }); 3917 + Handler.addTo = function(map2, name) { 3918 + map2.addHandler(name, this); 3919 + return this; 3920 + }; 3921 + var Mixin = { Events }; 3922 + var START = Browser.touch ? "touchstart mousedown" : "mousedown"; 3923 + var Draggable = Evented.extend({ 3924 + options: { 3925 + // @section 3926 + // @aka Draggable options 3927 + // @option clickTolerance: Number = 3 3928 + // The max number of pixels a user can shift the mouse pointer during a click 3929 + // for it to be considered a valid click (as opposed to a mouse drag). 3930 + clickTolerance: 3 3931 + }, 3932 + // @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options) 3933 + // Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default). 3934 + initialize: function(element, dragStartTarget, preventOutline2, options) { 3935 + setOptions(this, options); 3936 + this._element = element; 3937 + this._dragStartTarget = dragStartTarget || element; 3938 + this._preventOutline = preventOutline2; 3939 + }, 3940 + // @method enable() 3941 + // Enables the dragging ability 3942 + enable: function() { 3943 + if (this._enabled) { 3944 + return; 3945 + } 3946 + on(this._dragStartTarget, START, this._onDown, this); 3947 + this._enabled = true; 3948 + }, 3949 + // @method disable() 3950 + // Disables the dragging ability 3951 + disable: function() { 3952 + if (!this._enabled) { 3953 + return; 3954 + } 3955 + if (Draggable._dragging === this) { 3956 + this.finishDrag(true); 3957 + } 3958 + off(this._dragStartTarget, START, this._onDown, this); 3959 + this._enabled = false; 3960 + this._moved = false; 3961 + }, 3962 + _onDown: function(e) { 3963 + if (!this._enabled) { 3964 + return; 3965 + } 3966 + this._moved = false; 3967 + if (hasClass(this._element, "leaflet-zoom-anim")) { 3968 + return; 3969 + } 3970 + if (e.touches && e.touches.length !== 1) { 3971 + if (Draggable._dragging === this) { 3972 + this.finishDrag(); 3973 + } 3974 + return; 3975 + } 3976 + if (Draggable._dragging || e.shiftKey || e.which !== 1 && e.button !== 1 && !e.touches) { 3977 + return; 3978 + } 3979 + Draggable._dragging = this; 3980 + if (this._preventOutline) { 3981 + preventOutline(this._element); 3982 + } 3983 + disableImageDrag(); 3984 + disableTextSelection(); 3985 + if (this._moving) { 3986 + return; 3987 + } 3988 + this.fire("down"); 3989 + var first = e.touches ? e.touches[0] : e, sizedParent = getSizedParentNode(this._element); 3990 + this._startPoint = new Point(first.clientX, first.clientY); 3991 + this._startPos = getPosition(this._element); 3992 + this._parentScale = getScale(sizedParent); 3993 + var mouseevent = e.type === "mousedown"; 3994 + on(document, mouseevent ? "mousemove" : "touchmove", this._onMove, this); 3995 + on(document, mouseevent ? "mouseup" : "touchend touchcancel", this._onUp, this); 3996 + }, 3997 + _onMove: function(e) { 3998 + if (!this._enabled) { 3999 + return; 4000 + } 4001 + if (e.touches && e.touches.length > 1) { 4002 + this._moved = true; 4003 + return; 4004 + } 4005 + var first = e.touches && e.touches.length === 1 ? e.touches[0] : e, offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint); 4006 + if (!offset.x && !offset.y) { 4007 + return; 4008 + } 4009 + if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { 4010 + return; 4011 + } 4012 + offset.x /= this._parentScale.x; 4013 + offset.y /= this._parentScale.y; 4014 + preventDefault(e); 4015 + if (!this._moved) { 4016 + this.fire("dragstart"); 4017 + this._moved = true; 4018 + addClass(document.body, "leaflet-dragging"); 4019 + this._lastTarget = e.target || e.srcElement; 4020 + if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) { 4021 + this._lastTarget = this._lastTarget.correspondingUseElement; 4022 + } 4023 + addClass(this._lastTarget, "leaflet-drag-target"); 4024 + } 4025 + this._newPos = this._startPos.add(offset); 4026 + this._moving = true; 4027 + this._lastEvent = e; 4028 + this._updatePosition(); 4029 + }, 4030 + _updatePosition: function() { 4031 + var e = { originalEvent: this._lastEvent }; 4032 + this.fire("predrag", e); 4033 + setPosition(this._element, this._newPos); 4034 + this.fire("drag", e); 4035 + }, 4036 + _onUp: function() { 4037 + if (!this._enabled) { 4038 + return; 4039 + } 4040 + this.finishDrag(); 4041 + }, 4042 + finishDrag: function(noInertia) { 4043 + removeClass(document.body, "leaflet-dragging"); 4044 + if (this._lastTarget) { 4045 + removeClass(this._lastTarget, "leaflet-drag-target"); 4046 + this._lastTarget = null; 4047 + } 4048 + off(document, "mousemove touchmove", this._onMove, this); 4049 + off(document, "mouseup touchend touchcancel", this._onUp, this); 4050 + enableImageDrag(); 4051 + enableTextSelection(); 4052 + var fireDragend = this._moved && this._moving; 4053 + this._moving = false; 4054 + Draggable._dragging = false; 4055 + if (fireDragend) { 4056 + this.fire("dragend", { 4057 + noInertia, 4058 + distance: this._newPos.distanceTo(this._startPos) 4059 + }); 4060 + } 4061 + } 4062 + }); 4063 + function clipPolygon(points, bounds, round) { 4064 + var clippedPoints, edges = [1, 4, 2, 8], i, j, k, a, b, len, edge2, p; 4065 + for (i = 0, len = points.length; i < len; i++) { 4066 + points[i]._code = _getBitCode(points[i], bounds); 4067 + } 4068 + for (k = 0; k < 4; k++) { 4069 + edge2 = edges[k]; 4070 + clippedPoints = []; 4071 + for (i = 0, len = points.length, j = len - 1; i < len; j = i++) { 4072 + a = points[i]; 4073 + b = points[j]; 4074 + if (!(a._code & edge2)) { 4075 + if (b._code & edge2) { 4076 + p = _getEdgeIntersection(b, a, edge2, bounds, round); 4077 + p._code = _getBitCode(p, bounds); 4078 + clippedPoints.push(p); 4079 + } 4080 + clippedPoints.push(a); 4081 + } else if (!(b._code & edge2)) { 4082 + p = _getEdgeIntersection(b, a, edge2, bounds, round); 4083 + p._code = _getBitCode(p, bounds); 4084 + clippedPoints.push(p); 4085 + } 4086 + } 4087 + points = clippedPoints; 4088 + } 4089 + return points; 4090 + } 4091 + function polygonCenter(latlngs, crs) { 4092 + var i, j, p1, p2, f, area, x, y, center; 4093 + if (!latlngs || latlngs.length === 0) { 4094 + throw new Error("latlngs not passed"); 4095 + } 4096 + if (!isFlat(latlngs)) { 4097 + console.warn("latlngs are not flat! Only the first ring will be used"); 4098 + latlngs = latlngs[0]; 4099 + } 4100 + var centroidLatLng = toLatLng([0, 0]); 4101 + var bounds = toLatLngBounds(latlngs); 4102 + var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 4103 + if (areaBounds < 1700) { 4104 + centroidLatLng = centroid(latlngs); 4105 + } 4106 + var len = latlngs.length; 4107 + var points = []; 4108 + for (i = 0; i < len; i++) { 4109 + var latlng = toLatLng(latlngs[i]); 4110 + points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 4111 + } 4112 + area = x = y = 0; 4113 + for (i = 0, j = len - 1; i < len; j = i++) { 4114 + p1 = points[i]; 4115 + p2 = points[j]; 4116 + f = p1.y * p2.x - p2.y * p1.x; 4117 + x += (p1.x + p2.x) * f; 4118 + y += (p1.y + p2.y) * f; 4119 + area += f * 3; 4120 + } 4121 + if (area === 0) { 4122 + center = points[0]; 4123 + } else { 4124 + center = [x / area, y / area]; 4125 + } 4126 + var latlngCenter = crs.unproject(toPoint(center)); 4127 + return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 4128 + } 4129 + function centroid(coords) { 4130 + var latSum = 0; 4131 + var lngSum = 0; 4132 + var len = 0; 4133 + for (var i = 0; i < coords.length; i++) { 4134 + var latlng = toLatLng(coords[i]); 4135 + latSum += latlng.lat; 4136 + lngSum += latlng.lng; 4137 + len++; 4138 + } 4139 + return toLatLng([latSum / len, lngSum / len]); 4140 + } 4141 + var PolyUtil = { 4142 + __proto__: null, 4143 + clipPolygon, 4144 + polygonCenter, 4145 + centroid 4146 + }; 4147 + function simplify(points, tolerance) { 4148 + if (!tolerance || !points.length) { 4149 + return points.slice(); 4150 + } 4151 + var sqTolerance = tolerance * tolerance; 4152 + points = _reducePoints(points, sqTolerance); 4153 + points = _simplifyDP(points, sqTolerance); 4154 + return points; 4155 + } 4156 + function pointToSegmentDistance(p, p1, p2) { 4157 + return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true)); 4158 + } 4159 + function closestPointOnSegment(p, p1, p2) { 4160 + return _sqClosestPointOnSegment(p, p1, p2); 4161 + } 4162 + function _simplifyDP(points, sqTolerance) { 4163 + var len = points.length, ArrayConstructor = typeof Uint8Array !== "undefined" ? Uint8Array : Array, markers = new ArrayConstructor(len); 4164 + markers[0] = markers[len - 1] = 1; 4165 + _simplifyDPStep(points, markers, sqTolerance, 0, len - 1); 4166 + var i, newPoints = []; 4167 + for (i = 0; i < len; i++) { 4168 + if (markers[i]) { 4169 + newPoints.push(points[i]); 4170 + } 4171 + } 4172 + return newPoints; 4173 + } 4174 + function _simplifyDPStep(points, markers, sqTolerance, first, last) { 4175 + var maxSqDist = 0, index2, i, sqDist; 4176 + for (i = first + 1; i <= last - 1; i++) { 4177 + sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true); 4178 + if (sqDist > maxSqDist) { 4179 + index2 = i; 4180 + maxSqDist = sqDist; 4181 + } 4182 + } 4183 + if (maxSqDist > sqTolerance) { 4184 + markers[index2] = 1; 4185 + _simplifyDPStep(points, markers, sqTolerance, first, index2); 4186 + _simplifyDPStep(points, markers, sqTolerance, index2, last); 4187 + } 4188 + } 4189 + function _reducePoints(points, sqTolerance) { 4190 + var reducedPoints = [points[0]]; 4191 + for (var i = 1, prev = 0, len = points.length; i < len; i++) { 4192 + if (_sqDist(points[i], points[prev]) > sqTolerance) { 4193 + reducedPoints.push(points[i]); 4194 + prev = i; 4195 + } 4196 + } 4197 + if (prev < len - 1) { 4198 + reducedPoints.push(points[len - 1]); 4199 + } 4200 + return reducedPoints; 4201 + } 4202 + var _lastCode; 4203 + function clipSegment(a, b, bounds, useLastCode, round) { 4204 + var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds), codeB = _getBitCode(b, bounds), codeOut, p, newCode; 4205 + _lastCode = codeB; 4206 + while (true) { 4207 + if (!(codeA | codeB)) { 4208 + return [a, b]; 4209 + } 4210 + if (codeA & codeB) { 4211 + return false; 4212 + } 4213 + codeOut = codeA || codeB; 4214 + p = _getEdgeIntersection(a, b, codeOut, bounds, round); 4215 + newCode = _getBitCode(p, bounds); 4216 + if (codeOut === codeA) { 4217 + a = p; 4218 + codeA = newCode; 4219 + } else { 4220 + b = p; 4221 + codeB = newCode; 4222 + } 4223 + } 4224 + } 4225 + function _getEdgeIntersection(a, b, code, bounds, round) { 4226 + var dx = b.x - a.x, dy = b.y - a.y, min = bounds.min, max = bounds.max, x, y; 4227 + if (code & 8) { 4228 + x = a.x + dx * (max.y - a.y) / dy; 4229 + y = max.y; 4230 + } else if (code & 4) { 4231 + x = a.x + dx * (min.y - a.y) / dy; 4232 + y = min.y; 4233 + } else if (code & 2) { 4234 + x = max.x; 4235 + y = a.y + dy * (max.x - a.x) / dx; 4236 + } else if (code & 1) { 4237 + x = min.x; 4238 + y = a.y + dy * (min.x - a.x) / dx; 4239 + } 4240 + return new Point(x, y, round); 4241 + } 4242 + function _getBitCode(p, bounds) { 4243 + var code = 0; 4244 + if (p.x < bounds.min.x) { 4245 + code |= 1; 4246 + } else if (p.x > bounds.max.x) { 4247 + code |= 2; 4248 + } 4249 + if (p.y < bounds.min.y) { 4250 + code |= 4; 4251 + } else if (p.y > bounds.max.y) { 4252 + code |= 8; 4253 + } 4254 + return code; 4255 + } 4256 + function _sqDist(p1, p2) { 4257 + var dx = p2.x - p1.x, dy = p2.y - p1.y; 4258 + return dx * dx + dy * dy; 4259 + } 4260 + function _sqClosestPointOnSegment(p, p1, p2, sqDist) { 4261 + var x = p1.x, y = p1.y, dx = p2.x - x, dy = p2.y - y, dot = dx * dx + dy * dy, t; 4262 + if (dot > 0) { 4263 + t = ((p.x - x) * dx + (p.y - y) * dy) / dot; 4264 + if (t > 1) { 4265 + x = p2.x; 4266 + y = p2.y; 4267 + } else if (t > 0) { 4268 + x += dx * t; 4269 + y += dy * t; 4270 + } 4271 + } 4272 + dx = p.x - x; 4273 + dy = p.y - y; 4274 + return sqDist ? dx * dx + dy * dy : new Point(x, y); 4275 + } 4276 + function isFlat(latlngs) { 4277 + return !isArray(latlngs[0]) || typeof latlngs[0][0] !== "object" && typeof latlngs[0][0] !== "undefined"; 4278 + } 4279 + function _flat(latlngs) { 4280 + console.warn("Deprecated use of _flat, please use L.LineUtil.isFlat instead."); 4281 + return isFlat(latlngs); 4282 + } 4283 + function polylineCenter(latlngs, crs) { 4284 + var i, halfDist, segDist, dist, p1, p2, ratio, center; 4285 + if (!latlngs || latlngs.length === 0) { 4286 + throw new Error("latlngs not passed"); 4287 + } 4288 + if (!isFlat(latlngs)) { 4289 + console.warn("latlngs are not flat! Only the first ring will be used"); 4290 + latlngs = latlngs[0]; 4291 + } 4292 + var centroidLatLng = toLatLng([0, 0]); 4293 + var bounds = toLatLngBounds(latlngs); 4294 + var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest()); 4295 + if (areaBounds < 1700) { 4296 + centroidLatLng = centroid(latlngs); 4297 + } 4298 + var len = latlngs.length; 4299 + var points = []; 4300 + for (i = 0; i < len; i++) { 4301 + var latlng = toLatLng(latlngs[i]); 4302 + points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng]))); 4303 + } 4304 + for (i = 0, halfDist = 0; i < len - 1; i++) { 4305 + halfDist += points[i].distanceTo(points[i + 1]) / 2; 4306 + } 4307 + if (halfDist === 0) { 4308 + center = points[0]; 4309 + } else { 4310 + for (i = 0, dist = 0; i < len - 1; i++) { 4311 + p1 = points[i]; 4312 + p2 = points[i + 1]; 4313 + segDist = p1.distanceTo(p2); 4314 + dist += segDist; 4315 + if (dist > halfDist) { 4316 + ratio = (dist - halfDist) / segDist; 4317 + center = [ 4318 + p2.x - ratio * (p2.x - p1.x), 4319 + p2.y - ratio * (p2.y - p1.y) 4320 + ]; 4321 + break; 4322 + } 4323 + } 4324 + } 4325 + var latlngCenter = crs.unproject(toPoint(center)); 4326 + return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]); 4327 + } 4328 + var LineUtil = { 4329 + __proto__: null, 4330 + simplify, 4331 + pointToSegmentDistance, 4332 + closestPointOnSegment, 4333 + clipSegment, 4334 + _getEdgeIntersection, 4335 + _getBitCode, 4336 + _sqClosestPointOnSegment, 4337 + isFlat, 4338 + _flat, 4339 + polylineCenter 4340 + }; 4341 + var LonLat = { 4342 + project: function(latlng) { 4343 + return new Point(latlng.lng, latlng.lat); 4344 + }, 4345 + unproject: function(point) { 4346 + return new LatLng(point.y, point.x); 4347 + }, 4348 + bounds: new Bounds([-180, -90], [180, 90]) 4349 + }; 4350 + var Mercator = { 4351 + R: 6378137, 4352 + R_MINOR: 6356752314245179e-9, 4353 + bounds: new Bounds([-2003750834279e-5, -1549657073972e-5], [2003750834279e-5, 1876465623138e-5]), 4354 + project: function(latlng) { 4355 + var d = Math.PI / 180, r = this.R, y = latlng.lat * d, tmp = this.R_MINOR / r, e = Math.sqrt(1 - tmp * tmp), con = e * Math.sin(y); 4356 + var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2); 4357 + y = -r * Math.log(Math.max(ts, 1e-10)); 4358 + return new Point(latlng.lng * d * r, y); 4359 + }, 4360 + unproject: function(point) { 4361 + var d = 180 / Math.PI, r = this.R, tmp = this.R_MINOR / r, e = Math.sqrt(1 - tmp * tmp), ts = Math.exp(-point.y / r), phi = Math.PI / 2 - 2 * Math.atan(ts); 4362 + for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) { 4363 + con = e * Math.sin(phi); 4364 + con = Math.pow((1 - con) / (1 + con), e / 2); 4365 + dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi; 4366 + phi += dphi; 4367 + } 4368 + return new LatLng(phi * d, point.x * d / r); 4369 + } 4370 + }; 4371 + var index = { 4372 + __proto__: null, 4373 + LonLat, 4374 + Mercator, 4375 + SphericalMercator 4376 + }; 4377 + var EPSG3395 = extend({}, Earth, { 4378 + code: "EPSG:3395", 4379 + projection: Mercator, 4380 + transformation: function() { 4381 + var scale2 = 0.5 / (Math.PI * Mercator.R); 4382 + return toTransformation(scale2, 0.5, -scale2, 0.5); 4383 + }() 4384 + }); 4385 + var EPSG4326 = extend({}, Earth, { 4386 + code: "EPSG:4326", 4387 + projection: LonLat, 4388 + transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5) 4389 + }); 4390 + var Simple = extend({}, CRS, { 4391 + projection: LonLat, 4392 + transformation: toTransformation(1, 0, -1, 0), 4393 + scale: function(zoom2) { 4394 + return Math.pow(2, zoom2); 4395 + }, 4396 + zoom: function(scale2) { 4397 + return Math.log(scale2) / Math.LN2; 4398 + }, 4399 + distance: function(latlng1, latlng2) { 4400 + var dx = latlng2.lng - latlng1.lng, dy = latlng2.lat - latlng1.lat; 4401 + return Math.sqrt(dx * dx + dy * dy); 4402 + }, 4403 + infinite: true 4404 + }); 4405 + CRS.Earth = Earth; 4406 + CRS.EPSG3395 = EPSG3395; 4407 + CRS.EPSG3857 = EPSG3857; 4408 + CRS.EPSG900913 = EPSG900913; 4409 + CRS.EPSG4326 = EPSG4326; 4410 + CRS.Simple = Simple; 4411 + var Layer = Evented.extend({ 4412 + // Classes extending `L.Layer` will inherit the following options: 4413 + options: { 4414 + // @option pane: String = 'overlayPane' 4415 + // By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default. 4416 + pane: "overlayPane", 4417 + // @option attribution: String = null 4418 + // String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers. 4419 + attribution: null, 4420 + bubblingMouseEvents: true 4421 + }, 4422 + /* @section 4423 + * Classes extending `L.Layer` will inherit the following methods: 4424 + * 4425 + * @method addTo(map: Map|LayerGroup): this 4426 + * Adds the layer to the given map or layer group. 4427 + */ 4428 + addTo: function(map2) { 4429 + map2.addLayer(this); 4430 + return this; 4431 + }, 4432 + // @method remove: this 4433 + // Removes the layer from the map it is currently active on. 4434 + remove: function() { 4435 + return this.removeFrom(this._map || this._mapToAdd); 4436 + }, 4437 + // @method removeFrom(map: Map): this 4438 + // Removes the layer from the given map 4439 + // 4440 + // @alternative 4441 + // @method removeFrom(group: LayerGroup): this 4442 + // Removes the layer from the given `LayerGroup` 4443 + removeFrom: function(obj) { 4444 + if (obj) { 4445 + obj.removeLayer(this); 4446 + } 4447 + return this; 4448 + }, 4449 + // @method getPane(name? : String): HTMLElement 4450 + // Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer. 4451 + getPane: function(name) { 4452 + return this._map.getPane(name ? this.options[name] || name : this.options.pane); 4453 + }, 4454 + addInteractiveTarget: function(targetEl) { 4455 + this._map._targets[stamp(targetEl)] = this; 4456 + return this; 4457 + }, 4458 + removeInteractiveTarget: function(targetEl) { 4459 + delete this._map._targets[stamp(targetEl)]; 4460 + return this; 4461 + }, 4462 + // @method getAttribution: String 4463 + // Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution). 4464 + getAttribution: function() { 4465 + return this.options.attribution; 4466 + }, 4467 + _layerAdd: function(e) { 4468 + var map2 = e.target; 4469 + if (!map2.hasLayer(this)) { 4470 + return; 4471 + } 4472 + this._map = map2; 4473 + this._zoomAnimated = map2._zoomAnimated; 4474 + if (this.getEvents) { 4475 + var events = this.getEvents(); 4476 + map2.on(events, this); 4477 + this.once("remove", function() { 4478 + map2.off(events, this); 4479 + }, this); 4480 + } 4481 + this.onAdd(map2); 4482 + this.fire("add"); 4483 + map2.fire("layeradd", { layer: this }); 4484 + } 4485 + }); 4486 + Map2.include({ 4487 + // @method addLayer(layer: Layer): this 4488 + // Adds the given layer to the map 4489 + addLayer: function(layer) { 4490 + if (!layer._layerAdd) { 4491 + throw new Error("The provided object is not a Layer."); 4492 + } 4493 + var id = stamp(layer); 4494 + if (this._layers[id]) { 4495 + return this; 4496 + } 4497 + this._layers[id] = layer; 4498 + layer._mapToAdd = this; 4499 + if (layer.beforeAdd) { 4500 + layer.beforeAdd(this); 4501 + } 4502 + this.whenReady(layer._layerAdd, layer); 4503 + return this; 4504 + }, 4505 + // @method removeLayer(layer: Layer): this 4506 + // Removes the given layer from the map. 4507 + removeLayer: function(layer) { 4508 + var id = stamp(layer); 4509 + if (!this._layers[id]) { 4510 + return this; 4511 + } 4512 + if (this._loaded) { 4513 + layer.onRemove(this); 4514 + } 4515 + delete this._layers[id]; 4516 + if (this._loaded) { 4517 + this.fire("layerremove", { layer }); 4518 + layer.fire("remove"); 4519 + } 4520 + layer._map = layer._mapToAdd = null; 4521 + return this; 4522 + }, 4523 + // @method hasLayer(layer: Layer): Boolean 4524 + // Returns `true` if the given layer is currently added to the map 4525 + hasLayer: function(layer) { 4526 + return stamp(layer) in this._layers; 4527 + }, 4528 + /* @method eachLayer(fn: Function, context?: Object): this 4529 + * Iterates over the layers of the map, optionally specifying context of the iterator function. 4530 + * ``` 4531 + * map.eachLayer(function(layer){ 4532 + * layer.bindPopup('Hello'); 4533 + * }); 4534 + * ``` 4535 + */ 4536 + eachLayer: function(method, context) { 4537 + for (var i in this._layers) { 4538 + method.call(context, this._layers[i]); 4539 + } 4540 + return this; 4541 + }, 4542 + _addLayers: function(layers2) { 4543 + layers2 = layers2 ? isArray(layers2) ? layers2 : [layers2] : []; 4544 + for (var i = 0, len = layers2.length; i < len; i++) { 4545 + this.addLayer(layers2[i]); 4546 + } 4547 + }, 4548 + _addZoomLimit: function(layer) { 4549 + if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) { 4550 + this._zoomBoundLayers[stamp(layer)] = layer; 4551 + this._updateZoomLevels(); 4552 + } 4553 + }, 4554 + _removeZoomLimit: function(layer) { 4555 + var id = stamp(layer); 4556 + if (this._zoomBoundLayers[id]) { 4557 + delete this._zoomBoundLayers[id]; 4558 + this._updateZoomLevels(); 4559 + } 4560 + }, 4561 + _updateZoomLevels: function() { 4562 + var minZoom = Infinity, maxZoom = -Infinity, oldZoomSpan = this._getZoomSpan(); 4563 + for (var i in this._zoomBoundLayers) { 4564 + var options = this._zoomBoundLayers[i].options; 4565 + minZoom = options.minZoom === void 0 ? minZoom : Math.min(minZoom, options.minZoom); 4566 + maxZoom = options.maxZoom === void 0 ? maxZoom : Math.max(maxZoom, options.maxZoom); 4567 + } 4568 + this._layersMaxZoom = maxZoom === -Infinity ? void 0 : maxZoom; 4569 + this._layersMinZoom = minZoom === Infinity ? void 0 : minZoom; 4570 + if (oldZoomSpan !== this._getZoomSpan()) { 4571 + this.fire("zoomlevelschange"); 4572 + } 4573 + if (this.options.maxZoom === void 0 && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) { 4574 + this.setZoom(this._layersMaxZoom); 4575 + } 4576 + if (this.options.minZoom === void 0 && this._layersMinZoom && this.getZoom() < this._layersMinZoom) { 4577 + this.setZoom(this._layersMinZoom); 4578 + } 4579 + } 4580 + }); 4581 + var LayerGroup = Layer.extend({ 4582 + initialize: function(layers2, options) { 4583 + setOptions(this, options); 4584 + this._layers = {}; 4585 + var i, len; 4586 + if (layers2) { 4587 + for (i = 0, len = layers2.length; i < len; i++) { 4588 + this.addLayer(layers2[i]); 4589 + } 4590 + } 4591 + }, 4592 + // @method addLayer(layer: Layer): this 4593 + // Adds the given layer to the group. 4594 + addLayer: function(layer) { 4595 + var id = this.getLayerId(layer); 4596 + this._layers[id] = layer; 4597 + if (this._map) { 4598 + this._map.addLayer(layer); 4599 + } 4600 + return this; 4601 + }, 4602 + // @method removeLayer(layer: Layer): this 4603 + // Removes the given layer from the group. 4604 + // @alternative 4605 + // @method removeLayer(id: Number): this 4606 + // Removes the layer with the given internal ID from the group. 4607 + removeLayer: function(layer) { 4608 + var id = layer in this._layers ? layer : this.getLayerId(layer); 4609 + if (this._map && this._layers[id]) { 4610 + this._map.removeLayer(this._layers[id]); 4611 + } 4612 + delete this._layers[id]; 4613 + return this; 4614 + }, 4615 + // @method hasLayer(layer: Layer): Boolean 4616 + // Returns `true` if the given layer is currently added to the group. 4617 + // @alternative 4618 + // @method hasLayer(id: Number): Boolean 4619 + // Returns `true` if the given internal ID is currently added to the group. 4620 + hasLayer: function(layer) { 4621 + var layerId = typeof layer === "number" ? layer : this.getLayerId(layer); 4622 + return layerId in this._layers; 4623 + }, 4624 + // @method clearLayers(): this 4625 + // Removes all the layers from the group. 4626 + clearLayers: function() { 4627 + return this.eachLayer(this.removeLayer, this); 4628 + }, 4629 + // @method invoke(methodName: String, …): this 4630 + // Calls `methodName` on every layer contained in this group, passing any 4631 + // additional parameters. Has no effect if the layers contained do not 4632 + // implement `methodName`. 4633 + invoke: function(methodName) { 4634 + var args = Array.prototype.slice.call(arguments, 1), i, layer; 4635 + for (i in this._layers) { 4636 + layer = this._layers[i]; 4637 + if (layer[methodName]) { 4638 + layer[methodName].apply(layer, args); 4639 + } 4640 + } 4641 + return this; 4642 + }, 4643 + onAdd: function(map2) { 4644 + this.eachLayer(map2.addLayer, map2); 4645 + }, 4646 + onRemove: function(map2) { 4647 + this.eachLayer(map2.removeLayer, map2); 4648 + }, 4649 + // @method eachLayer(fn: Function, context?: Object): this 4650 + // Iterates over the layers of the group, optionally specifying context of the iterator function. 4651 + // ```js 4652 + // group.eachLayer(function (layer) { 4653 + // layer.bindPopup('Hello'); 4654 + // }); 4655 + // ``` 4656 + eachLayer: function(method, context) { 4657 + for (var i in this._layers) { 4658 + method.call(context, this._layers[i]); 4659 + } 4660 + return this; 4661 + }, 4662 + // @method getLayer(id: Number): Layer 4663 + // Returns the layer with the given internal ID. 4664 + getLayer: function(id) { 4665 + return this._layers[id]; 4666 + }, 4667 + // @method getLayers(): Layer[] 4668 + // Returns an array of all the layers added to the group. 4669 + getLayers: function() { 4670 + var layers2 = []; 4671 + this.eachLayer(layers2.push, layers2); 4672 + return layers2; 4673 + }, 4674 + // @method setZIndex(zIndex: Number): this 4675 + // Calls `setZIndex` on every layer contained in this group, passing the z-index. 4676 + setZIndex: function(zIndex) { 4677 + return this.invoke("setZIndex", zIndex); 4678 + }, 4679 + // @method getLayerId(layer: Layer): Number 4680 + // Returns the internal ID for a layer 4681 + getLayerId: function(layer) { 4682 + return stamp(layer); 4683 + } 4684 + }); 4685 + var layerGroup2 = function(layers2, options) { 4686 + return new LayerGroup(layers2, options); 4687 + }; 4688 + var FeatureGroup = LayerGroup.extend({ 4689 + addLayer: function(layer) { 4690 + if (this.hasLayer(layer)) { 4691 + return this; 4692 + } 4693 + layer.addEventParent(this); 4694 + LayerGroup.prototype.addLayer.call(this, layer); 4695 + return this.fire("layeradd", { layer }); 4696 + }, 4697 + removeLayer: function(layer) { 4698 + if (!this.hasLayer(layer)) { 4699 + return this; 4700 + } 4701 + if (layer in this._layers) { 4702 + layer = this._layers[layer]; 4703 + } 4704 + layer.removeEventParent(this); 4705 + LayerGroup.prototype.removeLayer.call(this, layer); 4706 + return this.fire("layerremove", { layer }); 4707 + }, 4708 + // @method setStyle(style: Path options): this 4709 + // Sets the given path options to each layer of the group that has a `setStyle` method. 4710 + setStyle: function(style2) { 4711 + return this.invoke("setStyle", style2); 4712 + }, 4713 + // @method bringToFront(): this 4714 + // Brings the layer group to the top of all other layers 4715 + bringToFront: function() { 4716 + return this.invoke("bringToFront"); 4717 + }, 4718 + // @method bringToBack(): this 4719 + // Brings the layer group to the back of all other layers 4720 + bringToBack: function() { 4721 + return this.invoke("bringToBack"); 4722 + }, 4723 + // @method getBounds(): LatLngBounds 4724 + // Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children). 4725 + getBounds: function() { 4726 + var bounds = new LatLngBounds(); 4727 + for (var id in this._layers) { 4728 + var layer = this._layers[id]; 4729 + bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng()); 4730 + } 4731 + return bounds; 4732 + } 4733 + }); 4734 + var featureGroup2 = function(layers2, options) { 4735 + return new FeatureGroup(layers2, options); 4736 + }; 4737 + var Icon = Class.extend({ 4738 + /* @section 4739 + * @aka Icon options 4740 + * 4741 + * @option iconUrl: String = null 4742 + * **(required)** The URL to the icon image (absolute or relative to your script path). 4743 + * 4744 + * @option iconRetinaUrl: String = null 4745 + * The URL to a retina sized version of the icon image (absolute or relative to your 4746 + * script path). Used for Retina screen devices. 4747 + * 4748 + * @option iconSize: Point = null 4749 + * Size of the icon image in pixels. 4750 + * 4751 + * @option iconAnchor: Point = null 4752 + * The coordinates of the "tip" of the icon (relative to its top left corner). The icon 4753 + * will be aligned so that this point is at the marker's geographical location. Centered 4754 + * by default if size is specified, also can be set in CSS with negative margins. 4755 + * 4756 + * @option popupAnchor: Point = [0, 0] 4757 + * The coordinates of the point from which popups will "open", relative to the icon anchor. 4758 + * 4759 + * @option tooltipAnchor: Point = [0, 0] 4760 + * The coordinates of the point from which tooltips will "open", relative to the icon anchor. 4761 + * 4762 + * @option shadowUrl: String = null 4763 + * The URL to the icon shadow image. If not specified, no shadow image will be created. 4764 + * 4765 + * @option shadowRetinaUrl: String = null 4766 + * 4767 + * @option shadowSize: Point = null 4768 + * Size of the shadow image in pixels. 4769 + * 4770 + * @option shadowAnchor: Point = null 4771 + * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same 4772 + * as iconAnchor if not specified). 4773 + * 4774 + * @option className: String = '' 4775 + * A custom class name to assign to both icon and shadow images. Empty by default. 4776 + */ 4777 + options: { 4778 + popupAnchor: [0, 0], 4779 + tooltipAnchor: [0, 0], 4780 + // @option crossOrigin: Boolean|String = false 4781 + // Whether the crossOrigin attribute will be added to the tiles. 4782 + // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. 4783 + // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 4784 + crossOrigin: false 4785 + }, 4786 + initialize: function(options) { 4787 + setOptions(this, options); 4788 + }, 4789 + // @method createIcon(oldIcon?: HTMLElement): HTMLElement 4790 + // Called internally when the icon has to be shown, returns a `<img>` HTML element 4791 + // styled according to the options. 4792 + createIcon: function(oldIcon) { 4793 + return this._createIcon("icon", oldIcon); 4794 + }, 4795 + // @method createShadow(oldIcon?: HTMLElement): HTMLElement 4796 + // As `createIcon`, but for the shadow beneath it. 4797 + createShadow: function(oldIcon) { 4798 + return this._createIcon("shadow", oldIcon); 4799 + }, 4800 + _createIcon: function(name, oldIcon) { 4801 + var src = this._getIconUrl(name); 4802 + if (!src) { 4803 + if (name === "icon") { 4804 + throw new Error("iconUrl not set in Icon options (see the docs)."); 4805 + } 4806 + return null; 4807 + } 4808 + var img = this._createImg(src, oldIcon && oldIcon.tagName === "IMG" ? oldIcon : null); 4809 + this._setIconStyles(img, name); 4810 + if (this.options.crossOrigin || this.options.crossOrigin === "") { 4811 + img.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin; 4812 + } 4813 + return img; 4814 + }, 4815 + _setIconStyles: function(img, name) { 4816 + var options = this.options; 4817 + var sizeOption = options[name + "Size"]; 4818 + if (typeof sizeOption === "number") { 4819 + sizeOption = [sizeOption, sizeOption]; 4820 + } 4821 + var size = toPoint(sizeOption), anchor = toPoint(name === "shadow" && options.shadowAnchor || options.iconAnchor || size && size.divideBy(2, true)); 4822 + img.className = "leaflet-marker-" + name + " " + (options.className || ""); 4823 + if (anchor) { 4824 + img.style.marginLeft = -anchor.x + "px"; 4825 + img.style.marginTop = -anchor.y + "px"; 4826 + } 4827 + if (size) { 4828 + img.style.width = size.x + "px"; 4829 + img.style.height = size.y + "px"; 4830 + } 4831 + }, 4832 + _createImg: function(src, el) { 4833 + el = el || document.createElement("img"); 4834 + el.src = src; 4835 + return el; 4836 + }, 4837 + _getIconUrl: function(name) { 4838 + return Browser.retina && this.options[name + "RetinaUrl"] || this.options[name + "Url"]; 4839 + } 4840 + }); 4841 + function icon(options) { 4842 + return new Icon(options); 4843 + } 4844 + var IconDefault = Icon.extend({ 4845 + options: { 4846 + iconUrl: "marker-icon.png", 4847 + iconRetinaUrl: "marker-icon-2x.png", 4848 + shadowUrl: "marker-shadow.png", 4849 + iconSize: [25, 41], 4850 + iconAnchor: [12, 41], 4851 + popupAnchor: [1, -34], 4852 + tooltipAnchor: [16, -28], 4853 + shadowSize: [41, 41] 4854 + }, 4855 + _getIconUrl: function(name) { 4856 + if (typeof IconDefault.imagePath !== "string") { 4857 + IconDefault.imagePath = this._detectIconPath(); 4858 + } 4859 + return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name); 4860 + }, 4861 + _stripUrl: function(path) { 4862 + var strip = function(str, re, idx) { 4863 + var match = re.exec(str); 4864 + return match && match[idx]; 4865 + }; 4866 + path = strip(path, /^url\((['"])?(.+)\1\)$/, 2); 4867 + return path && strip(path, /^(.*)marker-icon\.png$/, 1); 4868 + }, 4869 + _detectIconPath: function() { 4870 + var el = create$1("div", "leaflet-default-icon-path", document.body); 4871 + var path = getStyle(el, "background-image") || getStyle(el, "backgroundImage"); 4872 + document.body.removeChild(el); 4873 + path = this._stripUrl(path); 4874 + if (path) { 4875 + return path; 4876 + } 4877 + var link = document.querySelector('link[href$="leaflet.css"]'); 4878 + if (!link) { 4879 + return ""; 4880 + } 4881 + return link.href.substring(0, link.href.length - "leaflet.css".length - 1); 4882 + } 4883 + }); 4884 + var MarkerDrag = Handler.extend({ 4885 + initialize: function(marker3) { 4886 + this._marker = marker3; 4887 + }, 4888 + addHooks: function() { 4889 + var icon2 = this._marker._icon; 4890 + if (!this._draggable) { 4891 + this._draggable = new Draggable(icon2, icon2, true); 4892 + } 4893 + this._draggable.on({ 4894 + dragstart: this._onDragStart, 4895 + predrag: this._onPreDrag, 4896 + drag: this._onDrag, 4897 + dragend: this._onDragEnd 4898 + }, this).enable(); 4899 + addClass(icon2, "leaflet-marker-draggable"); 4900 + }, 4901 + removeHooks: function() { 4902 + this._draggable.off({ 4903 + dragstart: this._onDragStart, 4904 + predrag: this._onPreDrag, 4905 + drag: this._onDrag, 4906 + dragend: this._onDragEnd 4907 + }, this).disable(); 4908 + if (this._marker._icon) { 4909 + removeClass(this._marker._icon, "leaflet-marker-draggable"); 4910 + } 4911 + }, 4912 + moved: function() { 4913 + return this._draggable && this._draggable._moved; 4914 + }, 4915 + _adjustPan: function(e) { 4916 + var marker3 = this._marker, map2 = marker3._map, speed = this._marker.options.autoPanSpeed, padding = this._marker.options.autoPanPadding, iconPos = getPosition(marker3._icon), bounds = map2.getPixelBounds(), origin = map2.getPixelOrigin(); 4917 + var panBounds = toBounds( 4918 + bounds.min._subtract(origin).add(padding), 4919 + bounds.max._subtract(origin).subtract(padding) 4920 + ); 4921 + if (!panBounds.contains(iconPos)) { 4922 + var movement = toPoint( 4923 + (Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) - (Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x), 4924 + (Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) - (Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y) 4925 + ).multiplyBy(speed); 4926 + map2.panBy(movement, { animate: false }); 4927 + this._draggable._newPos._add(movement); 4928 + this._draggable._startPos._add(movement); 4929 + setPosition(marker3._icon, this._draggable._newPos); 4930 + this._onDrag(e); 4931 + this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 4932 + } 4933 + }, 4934 + _onDragStart: function() { 4935 + this._oldLatLng = this._marker.getLatLng(); 4936 + this._marker.closePopup && this._marker.closePopup(); 4937 + this._marker.fire("movestart").fire("dragstart"); 4938 + }, 4939 + _onPreDrag: function(e) { 4940 + if (this._marker.options.autoPan) { 4941 + cancelAnimFrame(this._panRequest); 4942 + this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e)); 4943 + } 4944 + }, 4945 + _onDrag: function(e) { 4946 + var marker3 = this._marker, shadow = marker3._shadow, iconPos = getPosition(marker3._icon), latlng = marker3._map.layerPointToLatLng(iconPos); 4947 + if (shadow) { 4948 + setPosition(shadow, iconPos); 4949 + } 4950 + marker3._latlng = latlng; 4951 + e.latlng = latlng; 4952 + e.oldLatLng = this._oldLatLng; 4953 + marker3.fire("move", e).fire("drag", e); 4954 + }, 4955 + _onDragEnd: function(e) { 4956 + cancelAnimFrame(this._panRequest); 4957 + delete this._oldLatLng; 4958 + this._marker.fire("moveend").fire("dragend", e); 4959 + } 4960 + }); 4961 + var Marker = Layer.extend({ 4962 + // @section 4963 + // @aka Marker options 4964 + options: { 4965 + // @option icon: Icon = * 4966 + // Icon instance to use for rendering the marker. 4967 + // See [Icon documentation](#L.Icon) for details on how to customize the marker icon. 4968 + // If not specified, a common instance of `L.Icon.Default` is used. 4969 + icon: new IconDefault(), 4970 + // Option inherited from "Interactive layer" abstract class 4971 + interactive: true, 4972 + // @option keyboard: Boolean = true 4973 + // Whether the marker can be tabbed to with a keyboard and clicked by pressing enter. 4974 + keyboard: true, 4975 + // @option title: String = '' 4976 + // Text for the browser tooltip that appear on marker hover (no tooltip by default). 4977 + // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 4978 + title: "", 4979 + // @option alt: String = 'Marker' 4980 + // Text for the `alt` attribute of the icon image. 4981 + // [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled). 4982 + alt: "Marker", 4983 + // @option zIndexOffset: Number = 0 4984 + // By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively). 4985 + zIndexOffset: 0, 4986 + // @option opacity: Number = 1.0 4987 + // The opacity of the marker. 4988 + opacity: 1, 4989 + // @option riseOnHover: Boolean = false 4990 + // If `true`, the marker will get on top of others when you hover the mouse over it. 4991 + riseOnHover: false, 4992 + // @option riseOffset: Number = 250 4993 + // The z-index offset used for the `riseOnHover` feature. 4994 + riseOffset: 250, 4995 + // @option pane: String = 'markerPane' 4996 + // `Map pane` where the markers icon will be added. 4997 + pane: "markerPane", 4998 + // @option shadowPane: String = 'shadowPane' 4999 + // `Map pane` where the markers shadow will be added. 5000 + shadowPane: "shadowPane", 5001 + // @option bubblingMouseEvents: Boolean = false 5002 + // When `true`, a mouse event on this marker will trigger the same event on the map 5003 + // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 5004 + bubblingMouseEvents: false, 5005 + // @option autoPanOnFocus: Boolean = true 5006 + // When `true`, the map will pan whenever the marker is focused (via 5007 + // e.g. pressing `tab` on the keyboard) to ensure the marker is 5008 + // visible within the map's bounds 5009 + autoPanOnFocus: true, 5010 + // @section Draggable marker options 5011 + // @option draggable: Boolean = false 5012 + // Whether the marker is draggable with mouse/touch or not. 5013 + draggable: false, 5014 + // @option autoPan: Boolean = false 5015 + // Whether to pan the map when dragging this marker near its edge or not. 5016 + autoPan: false, 5017 + // @option autoPanPadding: Point = Point(50, 50) 5018 + // Distance (in pixels to the left/right and to the top/bottom) of the 5019 + // map edge to start panning the map. 5020 + autoPanPadding: [50, 50], 5021 + // @option autoPanSpeed: Number = 10 5022 + // Number of pixels the map should pan by. 5023 + autoPanSpeed: 10 5024 + }, 5025 + /* @section 5026 + * 5027 + * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods: 5028 + */ 5029 + initialize: function(latlng, options) { 5030 + setOptions(this, options); 5031 + this._latlng = toLatLng(latlng); 5032 + }, 5033 + onAdd: function(map2) { 5034 + this._zoomAnimated = this._zoomAnimated && map2.options.markerZoomAnimation; 5035 + if (this._zoomAnimated) { 5036 + map2.on("zoomanim", this._animateZoom, this); 5037 + } 5038 + this._initIcon(); 5039 + this.update(); 5040 + }, 5041 + onRemove: function(map2) { 5042 + if (this.dragging && this.dragging.enabled()) { 5043 + this.options.draggable = true; 5044 + this.dragging.removeHooks(); 5045 + } 5046 + delete this.dragging; 5047 + if (this._zoomAnimated) { 5048 + map2.off("zoomanim", this._animateZoom, this); 5049 + } 5050 + this._removeIcon(); 5051 + this._removeShadow(); 5052 + }, 5053 + getEvents: function() { 5054 + return { 5055 + zoom: this.update, 5056 + viewreset: this.update 5057 + }; 5058 + }, 5059 + // @method getLatLng: LatLng 5060 + // Returns the current geographical position of the marker. 5061 + getLatLng: function() { 5062 + return this._latlng; 5063 + }, 5064 + // @method setLatLng(latlng: LatLng): this 5065 + // Changes the marker position to the given point. 5066 + setLatLng: function(latlng) { 5067 + var oldLatLng = this._latlng; 5068 + this._latlng = toLatLng(latlng); 5069 + this.update(); 5070 + return this.fire("move", { oldLatLng, latlng: this._latlng }); 5071 + }, 5072 + // @method setZIndexOffset(offset: Number): this 5073 + // Changes the [zIndex offset](#marker-zindexoffset) of the marker. 5074 + setZIndexOffset: function(offset) { 5075 + this.options.zIndexOffset = offset; 5076 + return this.update(); 5077 + }, 5078 + // @method getIcon: Icon 5079 + // Returns the current icon used by the marker 5080 + getIcon: function() { 5081 + return this.options.icon; 5082 + }, 5083 + // @method setIcon(icon: Icon): this 5084 + // Changes the marker icon. 5085 + setIcon: function(icon2) { 5086 + this.options.icon = icon2; 5087 + if (this._map) { 5088 + this._initIcon(); 5089 + this.update(); 5090 + } 5091 + if (this._popup) { 5092 + this.bindPopup(this._popup, this._popup.options); 5093 + } 5094 + return this; 5095 + }, 5096 + getElement: function() { 5097 + return this._icon; 5098 + }, 5099 + update: function() { 5100 + if (this._icon && this._map) { 5101 + var pos = this._map.latLngToLayerPoint(this._latlng).round(); 5102 + this._setPos(pos); 5103 + } 5104 + return this; 5105 + }, 5106 + _initIcon: function() { 5107 + var options = this.options, classToAdd = "leaflet-zoom-" + (this._zoomAnimated ? "animated" : "hide"); 5108 + var icon2 = options.icon.createIcon(this._icon), addIcon = false; 5109 + if (icon2 !== this._icon) { 5110 + if (this._icon) { 5111 + this._removeIcon(); 5112 + } 5113 + addIcon = true; 5114 + if (options.title) { 5115 + icon2.title = options.title; 5116 + } 5117 + if (icon2.tagName === "IMG") { 5118 + icon2.alt = options.alt || ""; 5119 + } 5120 + } 5121 + addClass(icon2, classToAdd); 5122 + if (options.keyboard) { 5123 + icon2.tabIndex = "0"; 5124 + icon2.setAttribute("role", "button"); 5125 + } 5126 + this._icon = icon2; 5127 + if (options.riseOnHover) { 5128 + this.on({ 5129 + mouseover: this._bringToFront, 5130 + mouseout: this._resetZIndex 5131 + }); 5132 + } 5133 + if (this.options.autoPanOnFocus) { 5134 + on(icon2, "focus", this._panOnFocus, this); 5135 + } 5136 + var newShadow = options.icon.createShadow(this._shadow), addShadow = false; 5137 + if (newShadow !== this._shadow) { 5138 + this._removeShadow(); 5139 + addShadow = true; 5140 + } 5141 + if (newShadow) { 5142 + addClass(newShadow, classToAdd); 5143 + newShadow.alt = ""; 5144 + } 5145 + this._shadow = newShadow; 5146 + if (options.opacity < 1) { 5147 + this._updateOpacity(); 5148 + } 5149 + if (addIcon) { 5150 + this.getPane().appendChild(this._icon); 5151 + } 5152 + this._initInteraction(); 5153 + if (newShadow && addShadow) { 5154 + this.getPane(options.shadowPane).appendChild(this._shadow); 5155 + } 5156 + }, 5157 + _removeIcon: function() { 5158 + if (this.options.riseOnHover) { 5159 + this.off({ 5160 + mouseover: this._bringToFront, 5161 + mouseout: this._resetZIndex 5162 + }); 5163 + } 5164 + if (this.options.autoPanOnFocus) { 5165 + off(this._icon, "focus", this._panOnFocus, this); 5166 + } 5167 + remove(this._icon); 5168 + this.removeInteractiveTarget(this._icon); 5169 + this._icon = null; 5170 + }, 5171 + _removeShadow: function() { 5172 + if (this._shadow) { 5173 + remove(this._shadow); 5174 + } 5175 + this._shadow = null; 5176 + }, 5177 + _setPos: function(pos) { 5178 + if (this._icon) { 5179 + setPosition(this._icon, pos); 5180 + } 5181 + if (this._shadow) { 5182 + setPosition(this._shadow, pos); 5183 + } 5184 + this._zIndex = pos.y + this.options.zIndexOffset; 5185 + this._resetZIndex(); 5186 + }, 5187 + _updateZIndex: function(offset) { 5188 + if (this._icon) { 5189 + this._icon.style.zIndex = this._zIndex + offset; 5190 + } 5191 + }, 5192 + _animateZoom: function(opt) { 5193 + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); 5194 + this._setPos(pos); 5195 + }, 5196 + _initInteraction: function() { 5197 + if (!this.options.interactive) { 5198 + return; 5199 + } 5200 + addClass(this._icon, "leaflet-interactive"); 5201 + this.addInteractiveTarget(this._icon); 5202 + if (MarkerDrag) { 5203 + var draggable = this.options.draggable; 5204 + if (this.dragging) { 5205 + draggable = this.dragging.enabled(); 5206 + this.dragging.disable(); 5207 + } 5208 + this.dragging = new MarkerDrag(this); 5209 + if (draggable) { 5210 + this.dragging.enable(); 5211 + } 5212 + } 5213 + }, 5214 + // @method setOpacity(opacity: Number): this 5215 + // Changes the opacity of the marker. 5216 + setOpacity: function(opacity) { 5217 + this.options.opacity = opacity; 5218 + if (this._map) { 5219 + this._updateOpacity(); 5220 + } 5221 + return this; 5222 + }, 5223 + _updateOpacity: function() { 5224 + var opacity = this.options.opacity; 5225 + if (this._icon) { 5226 + setOpacity(this._icon, opacity); 5227 + } 5228 + if (this._shadow) { 5229 + setOpacity(this._shadow, opacity); 5230 + } 5231 + }, 5232 + _bringToFront: function() { 5233 + this._updateZIndex(this.options.riseOffset); 5234 + }, 5235 + _resetZIndex: function() { 5236 + this._updateZIndex(0); 5237 + }, 5238 + _panOnFocus: function() { 5239 + var map2 = this._map; 5240 + if (!map2) { 5241 + return; 5242 + } 5243 + var iconOpts = this.options.icon.options; 5244 + var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0); 5245 + var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0); 5246 + map2.panInside(this._latlng, { 5247 + paddingTopLeft: anchor, 5248 + paddingBottomRight: size.subtract(anchor) 5249 + }); 5250 + }, 5251 + _getPopupAnchor: function() { 5252 + return this.options.icon.options.popupAnchor; 5253 + }, 5254 + _getTooltipAnchor: function() { 5255 + return this.options.icon.options.tooltipAnchor; 5256 + } 5257 + }); 5258 + function marker2(latlng, options) { 5259 + return new Marker(latlng, options); 5260 + } 5261 + var Path = Layer.extend({ 5262 + // @section 5263 + // @aka Path options 5264 + options: { 5265 + // @option stroke: Boolean = true 5266 + // Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles. 5267 + stroke: true, 5268 + // @option color: String = '#3388ff' 5269 + // Stroke color 5270 + color: "#3388ff", 5271 + // @option weight: Number = 3 5272 + // Stroke width in pixels 5273 + weight: 3, 5274 + // @option opacity: Number = 1.0 5275 + // Stroke opacity 5276 + opacity: 1, 5277 + // @option lineCap: String= 'round' 5278 + // A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke. 5279 + lineCap: "round", 5280 + // @option lineJoin: String = 'round' 5281 + // A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke. 5282 + lineJoin: "round", 5283 + // @option dashArray: String = null 5284 + // A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). 5285 + dashArray: null, 5286 + // @option dashOffset: String = null 5287 + // A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility). 5288 + dashOffset: null, 5289 + // @option fill: Boolean = depends 5290 + // Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles. 5291 + fill: false, 5292 + // @option fillColor: String = * 5293 + // Fill color. Defaults to the value of the [`color`](#path-color) option 5294 + fillColor: null, 5295 + // @option fillOpacity: Number = 0.2 5296 + // Fill opacity. 5297 + fillOpacity: 0.2, 5298 + // @option fillRule: String = 'evenodd' 5299 + // A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined. 5300 + fillRule: "evenodd", 5301 + // className: '', 5302 + // Option inherited from "Interactive layer" abstract class 5303 + interactive: true, 5304 + // @option bubblingMouseEvents: Boolean = true 5305 + // When `true`, a mouse event on this path will trigger the same event on the map 5306 + // (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used). 5307 + bubblingMouseEvents: true 5308 + }, 5309 + beforeAdd: function(map2) { 5310 + this._renderer = map2.getRenderer(this); 5311 + }, 5312 + onAdd: function() { 5313 + this._renderer._initPath(this); 5314 + this._reset(); 5315 + this._renderer._addPath(this); 5316 + }, 5317 + onRemove: function() { 5318 + this._renderer._removePath(this); 5319 + }, 5320 + // @method redraw(): this 5321 + // Redraws the layer. Sometimes useful after you changed the coordinates that the path uses. 5322 + redraw: function() { 5323 + if (this._map) { 5324 + this._renderer._updatePath(this); 5325 + } 5326 + return this; 5327 + }, 5328 + // @method setStyle(style: Path options): this 5329 + // Changes the appearance of a Path based on the options in the `Path options` object. 5330 + setStyle: function(style2) { 5331 + setOptions(this, style2); 5332 + if (this._renderer) { 5333 + this._renderer._updateStyle(this); 5334 + if (this.options.stroke && style2 && Object.prototype.hasOwnProperty.call(style2, "weight")) { 5335 + this._updateBounds(); 5336 + } 5337 + } 5338 + return this; 5339 + }, 5340 + // @method bringToFront(): this 5341 + // Brings the layer to the top of all path layers. 5342 + bringToFront: function() { 5343 + if (this._renderer) { 5344 + this._renderer._bringToFront(this); 5345 + } 5346 + return this; 5347 + }, 5348 + // @method bringToBack(): this 5349 + // Brings the layer to the bottom of all path layers. 5350 + bringToBack: function() { 5351 + if (this._renderer) { 5352 + this._renderer._bringToBack(this); 5353 + } 5354 + return this; 5355 + }, 5356 + getElement: function() { 5357 + return this._path; 5358 + }, 5359 + _reset: function() { 5360 + this._project(); 5361 + this._update(); 5362 + }, 5363 + _clickTolerance: function() { 5364 + return (this.options.stroke ? this.options.weight / 2 : 0) + (this._renderer.options.tolerance || 0); 5365 + } 5366 + }); 5367 + var CircleMarker = Path.extend({ 5368 + // @section 5369 + // @aka CircleMarker options 5370 + options: { 5371 + fill: true, 5372 + // @option radius: Number = 10 5373 + // Radius of the circle marker, in pixels 5374 + radius: 10 5375 + }, 5376 + initialize: function(latlng, options) { 5377 + setOptions(this, options); 5378 + this._latlng = toLatLng(latlng); 5379 + this._radius = this.options.radius; 5380 + }, 5381 + // @method setLatLng(latLng: LatLng): this 5382 + // Sets the position of a circle marker to a new location. 5383 + setLatLng: function(latlng) { 5384 + var oldLatLng = this._latlng; 5385 + this._latlng = toLatLng(latlng); 5386 + this.redraw(); 5387 + return this.fire("move", { oldLatLng, latlng: this._latlng }); 5388 + }, 5389 + // @method getLatLng(): LatLng 5390 + // Returns the current geographical position of the circle marker 5391 + getLatLng: function() { 5392 + return this._latlng; 5393 + }, 5394 + // @method setRadius(radius: Number): this 5395 + // Sets the radius of a circle marker. Units are in pixels. 5396 + setRadius: function(radius) { 5397 + this.options.radius = this._radius = radius; 5398 + return this.redraw(); 5399 + }, 5400 + // @method getRadius(): Number 5401 + // Returns the current radius of the circle 5402 + getRadius: function() { 5403 + return this._radius; 5404 + }, 5405 + setStyle: function(options) { 5406 + var radius = options && options.radius || this._radius; 5407 + Path.prototype.setStyle.call(this, options); 5408 + this.setRadius(radius); 5409 + return this; 5410 + }, 5411 + _project: function() { 5412 + this._point = this._map.latLngToLayerPoint(this._latlng); 5413 + this._updateBounds(); 5414 + }, 5415 + _updateBounds: function() { 5416 + var r = this._radius, r2 = this._radiusY || r, w = this._clickTolerance(), p = [r + w, r2 + w]; 5417 + this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p)); 5418 + }, 5419 + _update: function() { 5420 + if (this._map) { 5421 + this._updatePath(); 5422 + } 5423 + }, 5424 + _updatePath: function() { 5425 + this._renderer._updateCircle(this); 5426 + }, 5427 + _empty: function() { 5428 + return this._radius && !this._renderer._bounds.intersects(this._pxBounds); 5429 + }, 5430 + // Needed by the `Canvas` renderer for interactivity 5431 + _containsPoint: function(p) { 5432 + return p.distanceTo(this._point) <= this._radius + this._clickTolerance(); 5433 + } 5434 + }); 5435 + function circleMarker2(latlng, options) { 5436 + return new CircleMarker(latlng, options); 5437 + } 5438 + var Circle = CircleMarker.extend({ 5439 + initialize: function(latlng, options, legacyOptions) { 5440 + if (typeof options === "number") { 5441 + options = extend({}, legacyOptions, { radius: options }); 5442 + } 5443 + setOptions(this, options); 5444 + this._latlng = toLatLng(latlng); 5445 + if (isNaN(this.options.radius)) { 5446 + throw new Error("Circle radius cannot be NaN"); 5447 + } 5448 + this._mRadius = this.options.radius; 5449 + }, 5450 + // @method setRadius(radius: Number): this 5451 + // Sets the radius of a circle. Units are in meters. 5452 + setRadius: function(radius) { 5453 + this._mRadius = radius; 5454 + return this.redraw(); 5455 + }, 5456 + // @method getRadius(): Number 5457 + // Returns the current radius of a circle. Units are in meters. 5458 + getRadius: function() { 5459 + return this._mRadius; 5460 + }, 5461 + // @method getBounds(): LatLngBounds 5462 + // Returns the `LatLngBounds` of the path. 5463 + getBounds: function() { 5464 + var half = [this._radius, this._radiusY || this._radius]; 5465 + return new LatLngBounds( 5466 + this._map.layerPointToLatLng(this._point.subtract(half)), 5467 + this._map.layerPointToLatLng(this._point.add(half)) 5468 + ); 5469 + }, 5470 + setStyle: Path.prototype.setStyle, 5471 + _project: function() { 5472 + var lng = this._latlng.lng, lat = this._latlng.lat, map2 = this._map, crs = map2.options.crs; 5473 + if (crs.distance === Earth.distance) { 5474 + var d = Math.PI / 180, latR = this._mRadius / Earth.R / d, top = map2.project([lat + latR, lng]), bottom = map2.project([lat - latR, lng]), p = top.add(bottom).divideBy(2), lat2 = map2.unproject(p).lat, lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) / (Math.cos(lat * d) * Math.cos(lat2 * d))) / d; 5475 + if (isNaN(lngR) || lngR === 0) { 5476 + lngR = latR / Math.cos(Math.PI / 180 * lat); 5477 + } 5478 + this._point = p.subtract(map2.getPixelOrigin()); 5479 + this._radius = isNaN(lngR) ? 0 : p.x - map2.project([lat2, lng - lngR]).x; 5480 + this._radiusY = p.y - top.y; 5481 + } else { 5482 + var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0])); 5483 + this._point = map2.latLngToLayerPoint(this._latlng); 5484 + this._radius = this._point.x - map2.latLngToLayerPoint(latlng2).x; 5485 + } 5486 + this._updateBounds(); 5487 + } 5488 + }); 5489 + function circle(latlng, options, legacyOptions) { 5490 + return new Circle(latlng, options, legacyOptions); 5491 + } 5492 + var Polyline = Path.extend({ 5493 + // @section 5494 + // @aka Polyline options 5495 + options: { 5496 + // @option smoothFactor: Number = 1.0 5497 + // How much to simplify the polyline on each zoom level. More means 5498 + // better performance and smoother look, and less means more accurate representation. 5499 + smoothFactor: 1, 5500 + // @option noClip: Boolean = false 5501 + // Disable polyline clipping. 5502 + noClip: false 5503 + }, 5504 + initialize: function(latlngs, options) { 5505 + setOptions(this, options); 5506 + this._setLatLngs(latlngs); 5507 + }, 5508 + // @method getLatLngs(): LatLng[] 5509 + // Returns an array of the points in the path, or nested arrays of points in case of multi-polyline. 5510 + getLatLngs: function() { 5511 + return this._latlngs; 5512 + }, 5513 + // @method setLatLngs(latlngs: LatLng[]): this 5514 + // Replaces all the points in the polyline with the given array of geographical points. 5515 + setLatLngs: function(latlngs) { 5516 + this._setLatLngs(latlngs); 5517 + return this.redraw(); 5518 + }, 5519 + // @method isEmpty(): Boolean 5520 + // Returns `true` if the Polyline has no LatLngs. 5521 + isEmpty: function() { 5522 + return !this._latlngs.length; 5523 + }, 5524 + // @method closestLayerPoint(p: Point): Point 5525 + // Returns the point closest to `p` on the Polyline. 5526 + closestLayerPoint: function(p) { 5527 + var minDistance = Infinity, minPoint = null, closest = _sqClosestPointOnSegment, p1, p2; 5528 + for (var j = 0, jLen = this._parts.length; j < jLen; j++) { 5529 + var points = this._parts[j]; 5530 + for (var i = 1, len = points.length; i < len; i++) { 5531 + p1 = points[i - 1]; 5532 + p2 = points[i]; 5533 + var sqDist = closest(p, p1, p2, true); 5534 + if (sqDist < minDistance) { 5535 + minDistance = sqDist; 5536 + minPoint = closest(p, p1, p2); 5537 + } 5538 + } 5539 + } 5540 + if (minPoint) { 5541 + minPoint.distance = Math.sqrt(minDistance); 5542 + } 5543 + return minPoint; 5544 + }, 5545 + // @method getCenter(): LatLng 5546 + // Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline. 5547 + getCenter: function() { 5548 + if (!this._map) { 5549 + throw new Error("Must add layer to map before using getCenter()"); 5550 + } 5551 + return polylineCenter(this._defaultShape(), this._map.options.crs); 5552 + }, 5553 + // @method getBounds(): LatLngBounds 5554 + // Returns the `LatLngBounds` of the path. 5555 + getBounds: function() { 5556 + return this._bounds; 5557 + }, 5558 + // @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this 5559 + // Adds a given point to the polyline. By default, adds to the first ring of 5560 + // the polyline in case of a multi-polyline, but can be overridden by passing 5561 + // a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)). 5562 + addLatLng: function(latlng, latlngs) { 5563 + latlngs = latlngs || this._defaultShape(); 5564 + latlng = toLatLng(latlng); 5565 + latlngs.push(latlng); 5566 + this._bounds.extend(latlng); 5567 + return this.redraw(); 5568 + }, 5569 + _setLatLngs: function(latlngs) { 5570 + this._bounds = new LatLngBounds(); 5571 + this._latlngs = this._convertLatLngs(latlngs); 5572 + }, 5573 + _defaultShape: function() { 5574 + return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0]; 5575 + }, 5576 + // recursively convert latlngs input into actual LatLng instances; calculate bounds along the way 5577 + _convertLatLngs: function(latlngs) { 5578 + var result = [], flat = isFlat(latlngs); 5579 + for (var i = 0, len = latlngs.length; i < len; i++) { 5580 + if (flat) { 5581 + result[i] = toLatLng(latlngs[i]); 5582 + this._bounds.extend(result[i]); 5583 + } else { 5584 + result[i] = this._convertLatLngs(latlngs[i]); 5585 + } 5586 + } 5587 + return result; 5588 + }, 5589 + _project: function() { 5590 + var pxBounds = new Bounds(); 5591 + this._rings = []; 5592 + this._projectLatlngs(this._latlngs, this._rings, pxBounds); 5593 + if (this._bounds.isValid() && pxBounds.isValid()) { 5594 + this._rawPxBounds = pxBounds; 5595 + this._updateBounds(); 5596 + } 5597 + }, 5598 + _updateBounds: function() { 5599 + var w = this._clickTolerance(), p = new Point(w, w); 5600 + if (!this._rawPxBounds) { 5601 + return; 5602 + } 5603 + this._pxBounds = new Bounds([ 5604 + this._rawPxBounds.min.subtract(p), 5605 + this._rawPxBounds.max.add(p) 5606 + ]); 5607 + }, 5608 + // recursively turns latlngs into a set of rings with projected coordinates 5609 + _projectLatlngs: function(latlngs, result, projectedBounds) { 5610 + var flat = latlngs[0] instanceof LatLng, len = latlngs.length, i, ring; 5611 + if (flat) { 5612 + ring = []; 5613 + for (i = 0; i < len; i++) { 5614 + ring[i] = this._map.latLngToLayerPoint(latlngs[i]); 5615 + projectedBounds.extend(ring[i]); 5616 + } 5617 + result.push(ring); 5618 + } else { 5619 + for (i = 0; i < len; i++) { 5620 + this._projectLatlngs(latlngs[i], result, projectedBounds); 5621 + } 5622 + } 5623 + }, 5624 + // clip polyline by renderer bounds so that we have less to render for performance 5625 + _clipPoints: function() { 5626 + var bounds = this._renderer._bounds; 5627 + this._parts = []; 5628 + if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 5629 + return; 5630 + } 5631 + if (this.options.noClip) { 5632 + this._parts = this._rings; 5633 + return; 5634 + } 5635 + var parts = this._parts, i, j, k, len, len2, segment, points; 5636 + for (i = 0, k = 0, len = this._rings.length; i < len; i++) { 5637 + points = this._rings[i]; 5638 + for (j = 0, len2 = points.length; j < len2 - 1; j++) { 5639 + segment = clipSegment(points[j], points[j + 1], bounds, j, true); 5640 + if (!segment) { 5641 + continue; 5642 + } 5643 + parts[k] = parts[k] || []; 5644 + parts[k].push(segment[0]); 5645 + if (segment[1] !== points[j + 1] || j === len2 - 2) { 5646 + parts[k].push(segment[1]); 5647 + k++; 5648 + } 5649 + } 5650 + } 5651 + }, 5652 + // simplify each clipped part of the polyline for performance 5653 + _simplifyPoints: function() { 5654 + var parts = this._parts, tolerance = this.options.smoothFactor; 5655 + for (var i = 0, len = parts.length; i < len; i++) { 5656 + parts[i] = simplify(parts[i], tolerance); 5657 + } 5658 + }, 5659 + _update: function() { 5660 + if (!this._map) { 5661 + return; 5662 + } 5663 + this._clipPoints(); 5664 + this._simplifyPoints(); 5665 + this._updatePath(); 5666 + }, 5667 + _updatePath: function() { 5668 + this._renderer._updatePoly(this); 5669 + }, 5670 + // Needed by the `Canvas` renderer for interactivity 5671 + _containsPoint: function(p, closed) { 5672 + var i, j, k, len, len2, part, w = this._clickTolerance(); 5673 + if (!this._pxBounds || !this._pxBounds.contains(p)) { 5674 + return false; 5675 + } 5676 + for (i = 0, len = this._parts.length; i < len; i++) { 5677 + part = this._parts[i]; 5678 + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 5679 + if (!closed && j === 0) { 5680 + continue; 5681 + } 5682 + if (pointToSegmentDistance(p, part[k], part[j]) <= w) { 5683 + return true; 5684 + } 5685 + } 5686 + } 5687 + return false; 5688 + } 5689 + }); 5690 + function polyline(latlngs, options) { 5691 + return new Polyline(latlngs, options); 5692 + } 5693 + Polyline._flat = _flat; 5694 + var Polygon = Polyline.extend({ 5695 + options: { 5696 + fill: true 5697 + }, 5698 + isEmpty: function() { 5699 + return !this._latlngs.length || !this._latlngs[0].length; 5700 + }, 5701 + // @method getCenter(): LatLng 5702 + // Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon. 5703 + getCenter: function() { 5704 + if (!this._map) { 5705 + throw new Error("Must add layer to map before using getCenter()"); 5706 + } 5707 + return polygonCenter(this._defaultShape(), this._map.options.crs); 5708 + }, 5709 + _convertLatLngs: function(latlngs) { 5710 + var result = Polyline.prototype._convertLatLngs.call(this, latlngs), len = result.length; 5711 + if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) { 5712 + result.pop(); 5713 + } 5714 + return result; 5715 + }, 5716 + _setLatLngs: function(latlngs) { 5717 + Polyline.prototype._setLatLngs.call(this, latlngs); 5718 + if (isFlat(this._latlngs)) { 5719 + this._latlngs = [this._latlngs]; 5720 + } 5721 + }, 5722 + _defaultShape: function() { 5723 + return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0]; 5724 + }, 5725 + _clipPoints: function() { 5726 + var bounds = this._renderer._bounds, w = this.options.weight, p = new Point(w, w); 5727 + bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p)); 5728 + this._parts = []; 5729 + if (!this._pxBounds || !this._pxBounds.intersects(bounds)) { 5730 + return; 5731 + } 5732 + if (this.options.noClip) { 5733 + this._parts = this._rings; 5734 + return; 5735 + } 5736 + for (var i = 0, len = this._rings.length, clipped; i < len; i++) { 5737 + clipped = clipPolygon(this._rings[i], bounds, true); 5738 + if (clipped.length) { 5739 + this._parts.push(clipped); 5740 + } 5741 + } 5742 + }, 5743 + _updatePath: function() { 5744 + this._renderer._updatePoly(this, true); 5745 + }, 5746 + // Needed by the `Canvas` renderer for interactivity 5747 + _containsPoint: function(p) { 5748 + var inside = false, part, p1, p2, i, j, k, len, len2; 5749 + if (!this._pxBounds || !this._pxBounds.contains(p)) { 5750 + return false; 5751 + } 5752 + for (i = 0, len = this._parts.length; i < len; i++) { 5753 + part = this._parts[i]; 5754 + for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) { 5755 + p1 = part[j]; 5756 + p2 = part[k]; 5757 + if (p1.y > p.y !== p2.y > p.y && p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x) { 5758 + inside = !inside; 5759 + } 5760 + } 5761 + } 5762 + return inside || Polyline.prototype._containsPoint.call(this, p, true); 5763 + } 5764 + }); 5765 + function polygon(latlngs, options) { 5766 + return new Polygon(latlngs, options); 5767 + } 5768 + var GeoJSON = FeatureGroup.extend({ 5769 + /* @section 5770 + * @aka GeoJSON options 5771 + * 5772 + * @option pointToLayer: Function = * 5773 + * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally 5774 + * called when data is added, passing the GeoJSON point feature and its `LatLng`. 5775 + * The default is to spawn a default `Marker`: 5776 + * ```js 5777 + * function(geoJsonPoint, latlng) { 5778 + * return L.marker(latlng); 5779 + * } 5780 + * ``` 5781 + * 5782 + * @option style: Function = * 5783 + * A `Function` defining the `Path options` for styling GeoJSON lines and polygons, 5784 + * called internally when data is added. 5785 + * The default value is to not override any defaults: 5786 + * ```js 5787 + * function (geoJsonFeature) { 5788 + * return {} 5789 + * } 5790 + * ``` 5791 + * 5792 + * @option onEachFeature: Function = * 5793 + * A `Function` that will be called once for each created `Feature`, after it has 5794 + * been created and styled. Useful for attaching events and popups to features. 5795 + * The default is to do nothing with the newly created layers: 5796 + * ```js 5797 + * function (feature, layer) {} 5798 + * ``` 5799 + * 5800 + * @option filter: Function = * 5801 + * A `Function` that will be used to decide whether to include a feature or not. 5802 + * The default is to include all features: 5803 + * ```js 5804 + * function (geoJsonFeature) { 5805 + * return true; 5806 + * } 5807 + * ``` 5808 + * Note: dynamically changing the `filter` option will have effect only on newly 5809 + * added data. It will _not_ re-evaluate already included features. 5810 + * 5811 + * @option coordsToLatLng: Function = * 5812 + * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s. 5813 + * The default is the `coordsToLatLng` static method. 5814 + * 5815 + * @option markersInheritOptions: Boolean = false 5816 + * Whether default Markers for "Point" type Features inherit from group options. 5817 + */ 5818 + initialize: function(geojson, options) { 5819 + setOptions(this, options); 5820 + this._layers = {}; 5821 + if (geojson) { 5822 + this.addData(geojson); 5823 + } 5824 + }, 5825 + // @method addData( <GeoJSON> data ): this 5826 + // Adds a GeoJSON object to the layer. 5827 + addData: function(geojson) { 5828 + var features = isArray(geojson) ? geojson : geojson.features, i, len, feature; 5829 + if (features) { 5830 + for (i = 0, len = features.length; i < len; i++) { 5831 + feature = features[i]; 5832 + if (feature.geometries || feature.geometry || feature.features || feature.coordinates) { 5833 + this.addData(feature); 5834 + } 5835 + } 5836 + return this; 5837 + } 5838 + var options = this.options; 5839 + if (options.filter && !options.filter(geojson)) { 5840 + return this; 5841 + } 5842 + var layer = geometryToLayer(geojson, options); 5843 + if (!layer) { 5844 + return this; 5845 + } 5846 + layer.feature = asFeature(geojson); 5847 + layer.defaultOptions = layer.options; 5848 + this.resetStyle(layer); 5849 + if (options.onEachFeature) { 5850 + options.onEachFeature(geojson, layer); 5851 + } 5852 + return this.addLayer(layer); 5853 + }, 5854 + // @method resetStyle( <Path> layer? ): this 5855 + // Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events. 5856 + // If `layer` is omitted, the style of all features in the current layer is reset. 5857 + resetStyle: function(layer) { 5858 + if (layer === void 0) { 5859 + return this.eachLayer(this.resetStyle, this); 5860 + } 5861 + layer.options = extend({}, layer.defaultOptions); 5862 + this._setLayerStyle(layer, this.options.style); 5863 + return this; 5864 + }, 5865 + // @method setStyle( <Function> style ): this 5866 + // Changes styles of GeoJSON vector layers with the given style function. 5867 + setStyle: function(style2) { 5868 + return this.eachLayer(function(layer) { 5869 + this._setLayerStyle(layer, style2); 5870 + }, this); 5871 + }, 5872 + _setLayerStyle: function(layer, style2) { 5873 + if (layer.setStyle) { 5874 + if (typeof style2 === "function") { 5875 + style2 = style2(layer.feature); 5876 + } 5877 + layer.setStyle(style2); 5878 + } 5879 + } 5880 + }); 5881 + function geometryToLayer(geojson, options) { 5882 + var geometry = geojson.type === "Feature" ? geojson.geometry : geojson, coords = geometry ? geometry.coordinates : null, layers2 = [], pointToLayer = options && options.pointToLayer, _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng, latlng, latlngs, i, len; 5883 + if (!coords && !geometry) { 5884 + return null; 5885 + } 5886 + switch (geometry.type) { 5887 + case "Point": 5888 + latlng = _coordsToLatLng(coords); 5889 + return _pointToLayer(pointToLayer, geojson, latlng, options); 5890 + case "MultiPoint": 5891 + for (i = 0, len = coords.length; i < len; i++) { 5892 + latlng = _coordsToLatLng(coords[i]); 5893 + layers2.push(_pointToLayer(pointToLayer, geojson, latlng, options)); 5894 + } 5895 + return new FeatureGroup(layers2); 5896 + case "LineString": 5897 + case "MultiLineString": 5898 + latlngs = coordsToLatLngs(coords, geometry.type === "LineString" ? 0 : 1, _coordsToLatLng); 5899 + return new Polyline(latlngs, options); 5900 + case "Polygon": 5901 + case "MultiPolygon": 5902 + latlngs = coordsToLatLngs(coords, geometry.type === "Polygon" ? 1 : 2, _coordsToLatLng); 5903 + return new Polygon(latlngs, options); 5904 + case "GeometryCollection": 5905 + for (i = 0, len = geometry.geometries.length; i < len; i++) { 5906 + var geoLayer = geometryToLayer({ 5907 + geometry: geometry.geometries[i], 5908 + type: "Feature", 5909 + properties: geojson.properties 5910 + }, options); 5911 + if (geoLayer) { 5912 + layers2.push(geoLayer); 5913 + } 5914 + } 5915 + return new FeatureGroup(layers2); 5916 + case "FeatureCollection": 5917 + for (i = 0, len = geometry.features.length; i < len; i++) { 5918 + var featureLayer = geometryToLayer(geometry.features[i], options); 5919 + if (featureLayer) { 5920 + layers2.push(featureLayer); 5921 + } 5922 + } 5923 + return new FeatureGroup(layers2); 5924 + default: 5925 + throw new Error("Invalid GeoJSON object."); 5926 + } 5927 + } 5928 + function _pointToLayer(pointToLayerFn, geojson, latlng, options) { 5929 + return pointToLayerFn ? pointToLayerFn(geojson, latlng) : new Marker(latlng, options && options.markersInheritOptions && options); 5930 + } 5931 + function coordsToLatLng(coords) { 5932 + return new LatLng(coords[1], coords[0], coords[2]); 5933 + } 5934 + function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) { 5935 + var latlngs = []; 5936 + for (var i = 0, len = coords.length, latlng; i < len; i++) { 5937 + latlng = levelsDeep ? coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) : (_coordsToLatLng || coordsToLatLng)(coords[i]); 5938 + latlngs.push(latlng); 5939 + } 5940 + return latlngs; 5941 + } 5942 + function latLngToCoords(latlng, precision) { 5943 + latlng = toLatLng(latlng); 5944 + return latlng.alt !== void 0 ? [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] : [formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)]; 5945 + } 5946 + function latLngsToCoords(latlngs, levelsDeep, closed, precision) { 5947 + var coords = []; 5948 + for (var i = 0, len = latlngs.length; i < len; i++) { 5949 + coords.push(levelsDeep ? latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) : latLngToCoords(latlngs[i], precision)); 5950 + } 5951 + if (!levelsDeep && closed && coords.length > 0) { 5952 + coords.push(coords[0].slice()); 5953 + } 5954 + return coords; 5955 + } 5956 + function getFeature(layer, newGeometry) { 5957 + return layer.feature ? extend({}, layer.feature, { geometry: newGeometry }) : asFeature(newGeometry); 5958 + } 5959 + function asFeature(geojson) { 5960 + if (geojson.type === "Feature" || geojson.type === "FeatureCollection") { 5961 + return geojson; 5962 + } 5963 + return { 5964 + type: "Feature", 5965 + properties: {}, 5966 + geometry: geojson 5967 + }; 5968 + } 5969 + var PointToGeoJSON = { 5970 + toGeoJSON: function(precision) { 5971 + return getFeature(this, { 5972 + type: "Point", 5973 + coordinates: latLngToCoords(this.getLatLng(), precision) 5974 + }); 5975 + } 5976 + }; 5977 + Marker.include(PointToGeoJSON); 5978 + Circle.include(PointToGeoJSON); 5979 + CircleMarker.include(PointToGeoJSON); 5980 + Polyline.include({ 5981 + toGeoJSON: function(precision) { 5982 + var multi = !isFlat(this._latlngs); 5983 + var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision); 5984 + return getFeature(this, { 5985 + type: (multi ? "Multi" : "") + "LineString", 5986 + coordinates: coords 5987 + }); 5988 + } 5989 + }); 5990 + Polygon.include({ 5991 + toGeoJSON: function(precision) { 5992 + var holes = !isFlat(this._latlngs), multi = holes && !isFlat(this._latlngs[0]); 5993 + var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision); 5994 + if (!holes) { 5995 + coords = [coords]; 5996 + } 5997 + return getFeature(this, { 5998 + type: (multi ? "Multi" : "") + "Polygon", 5999 + coordinates: coords 6000 + }); 6001 + } 6002 + }); 6003 + LayerGroup.include({ 6004 + toMultiPoint: function(precision) { 6005 + var coords = []; 6006 + this.eachLayer(function(layer) { 6007 + coords.push(layer.toGeoJSON(precision).geometry.coordinates); 6008 + }); 6009 + return getFeature(this, { 6010 + type: "MultiPoint", 6011 + coordinates: coords 6012 + }); 6013 + }, 6014 + // @method toGeoJSON(precision?: Number|false): Object 6015 + // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`. 6016 + // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`). 6017 + toGeoJSON: function(precision) { 6018 + var type = this.feature && this.feature.geometry && this.feature.geometry.type; 6019 + if (type === "MultiPoint") { 6020 + return this.toMultiPoint(precision); 6021 + } 6022 + var isGeometryCollection = type === "GeometryCollection", jsons = []; 6023 + this.eachLayer(function(layer) { 6024 + if (layer.toGeoJSON) { 6025 + var json = layer.toGeoJSON(precision); 6026 + if (isGeometryCollection) { 6027 + jsons.push(json.geometry); 6028 + } else { 6029 + var feature = asFeature(json); 6030 + if (feature.type === "FeatureCollection") { 6031 + jsons.push.apply(jsons, feature.features); 6032 + } else { 6033 + jsons.push(feature); 6034 + } 6035 + } 6036 + } 6037 + }); 6038 + if (isGeometryCollection) { 6039 + return getFeature(this, { 6040 + geometries: jsons, 6041 + type: "GeometryCollection" 6042 + }); 6043 + } 6044 + return { 6045 + type: "FeatureCollection", 6046 + features: jsons 6047 + }; 6048 + } 6049 + }); 6050 + function geoJSON(geojson, options) { 6051 + return new GeoJSON(geojson, options); 6052 + } 6053 + var geoJson = geoJSON; 6054 + var ImageOverlay = Layer.extend({ 6055 + // @section 6056 + // @aka ImageOverlay options 6057 + options: { 6058 + // @option opacity: Number = 1.0 6059 + // The opacity of the image overlay. 6060 + opacity: 1, 6061 + // @option alt: String = '' 6062 + // Text for the `alt` attribute of the image (useful for accessibility). 6063 + alt: "", 6064 + // @option interactive: Boolean = false 6065 + // If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered. 6066 + interactive: false, 6067 + // @option crossOrigin: Boolean|String = false 6068 + // Whether the crossOrigin attribute will be added to the image. 6069 + // If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data. 6070 + // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 6071 + crossOrigin: false, 6072 + // @option errorOverlayUrl: String = '' 6073 + // URL to the overlay image to show in place of the overlay that failed to load. 6074 + errorOverlayUrl: "", 6075 + // @option zIndex: Number = 1 6076 + // The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer. 6077 + zIndex: 1, 6078 + // @option className: String = '' 6079 + // A custom class name to assign to the image. Empty by default. 6080 + className: "" 6081 + }, 6082 + initialize: function(url, bounds, options) { 6083 + this._url = url; 6084 + this._bounds = toLatLngBounds(bounds); 6085 + setOptions(this, options); 6086 + }, 6087 + onAdd: function() { 6088 + if (!this._image) { 6089 + this._initImage(); 6090 + if (this.options.opacity < 1) { 6091 + this._updateOpacity(); 6092 + } 6093 + } 6094 + if (this.options.interactive) { 6095 + addClass(this._image, "leaflet-interactive"); 6096 + this.addInteractiveTarget(this._image); 6097 + } 6098 + this.getPane().appendChild(this._image); 6099 + this._reset(); 6100 + }, 6101 + onRemove: function() { 6102 + remove(this._image); 6103 + if (this.options.interactive) { 6104 + this.removeInteractiveTarget(this._image); 6105 + } 6106 + }, 6107 + // @method setOpacity(opacity: Number): this 6108 + // Sets the opacity of the overlay. 6109 + setOpacity: function(opacity) { 6110 + this.options.opacity = opacity; 6111 + if (this._image) { 6112 + this._updateOpacity(); 6113 + } 6114 + return this; 6115 + }, 6116 + setStyle: function(styleOpts) { 6117 + if (styleOpts.opacity) { 6118 + this.setOpacity(styleOpts.opacity); 6119 + } 6120 + return this; 6121 + }, 6122 + // @method bringToFront(): this 6123 + // Brings the layer to the top of all overlays. 6124 + bringToFront: function() { 6125 + if (this._map) { 6126 + toFront(this._image); 6127 + } 6128 + return this; 6129 + }, 6130 + // @method bringToBack(): this 6131 + // Brings the layer to the bottom of all overlays. 6132 + bringToBack: function() { 6133 + if (this._map) { 6134 + toBack(this._image); 6135 + } 6136 + return this; 6137 + }, 6138 + // @method setUrl(url: String): this 6139 + // Changes the URL of the image. 6140 + setUrl: function(url) { 6141 + this._url = url; 6142 + if (this._image) { 6143 + this._image.src = url; 6144 + } 6145 + return this; 6146 + }, 6147 + // @method setBounds(bounds: LatLngBounds): this 6148 + // Update the bounds that this ImageOverlay covers 6149 + setBounds: function(bounds) { 6150 + this._bounds = toLatLngBounds(bounds); 6151 + if (this._map) { 6152 + this._reset(); 6153 + } 6154 + return this; 6155 + }, 6156 + getEvents: function() { 6157 + var events = { 6158 + zoom: this._reset, 6159 + viewreset: this._reset 6160 + }; 6161 + if (this._zoomAnimated) { 6162 + events.zoomanim = this._animateZoom; 6163 + } 6164 + return events; 6165 + }, 6166 + // @method setZIndex(value: Number): this 6167 + // Changes the [zIndex](#imageoverlay-zindex) of the image overlay. 6168 + setZIndex: function(value) { 6169 + this.options.zIndex = value; 6170 + this._updateZIndex(); 6171 + return this; 6172 + }, 6173 + // @method getBounds(): LatLngBounds 6174 + // Get the bounds that this ImageOverlay covers 6175 + getBounds: function() { 6176 + return this._bounds; 6177 + }, 6178 + // @method getElement(): HTMLElement 6179 + // Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement) 6180 + // used by this overlay. 6181 + getElement: function() { 6182 + return this._image; 6183 + }, 6184 + _initImage: function() { 6185 + var wasElementSupplied = this._url.tagName === "IMG"; 6186 + var img = this._image = wasElementSupplied ? this._url : create$1("img"); 6187 + addClass(img, "leaflet-image-layer"); 6188 + if (this._zoomAnimated) { 6189 + addClass(img, "leaflet-zoom-animated"); 6190 + } 6191 + if (this.options.className) { 6192 + addClass(img, this.options.className); 6193 + } 6194 + img.onselectstart = falseFn; 6195 + img.onmousemove = falseFn; 6196 + img.onload = bind(this.fire, this, "load"); 6197 + img.onerror = bind(this._overlayOnError, this, "error"); 6198 + if (this.options.crossOrigin || this.options.crossOrigin === "") { 6199 + img.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin; 6200 + } 6201 + if (this.options.zIndex) { 6202 + this._updateZIndex(); 6203 + } 6204 + if (wasElementSupplied) { 6205 + this._url = img.src; 6206 + return; 6207 + } 6208 + img.src = this._url; 6209 + img.alt = this.options.alt; 6210 + }, 6211 + _animateZoom: function(e) { 6212 + var scale2 = this._map.getZoomScale(e.zoom), offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min; 6213 + setTransform(this._image, offset, scale2); 6214 + }, 6215 + _reset: function() { 6216 + var image = this._image, bounds = new Bounds( 6217 + this._map.latLngToLayerPoint(this._bounds.getNorthWest()), 6218 + this._map.latLngToLayerPoint(this._bounds.getSouthEast()) 6219 + ), size = bounds.getSize(); 6220 + setPosition(image, bounds.min); 6221 + image.style.width = size.x + "px"; 6222 + image.style.height = size.y + "px"; 6223 + }, 6224 + _updateOpacity: function() { 6225 + setOpacity(this._image, this.options.opacity); 6226 + }, 6227 + _updateZIndex: function() { 6228 + if (this._image && this.options.zIndex !== void 0 && this.options.zIndex !== null) { 6229 + this._image.style.zIndex = this.options.zIndex; 6230 + } 6231 + }, 6232 + _overlayOnError: function() { 6233 + this.fire("error"); 6234 + var errorUrl = this.options.errorOverlayUrl; 6235 + if (errorUrl && this._url !== errorUrl) { 6236 + this._url = errorUrl; 6237 + this._image.src = errorUrl; 6238 + } 6239 + }, 6240 + // @method getCenter(): LatLng 6241 + // Returns the center of the ImageOverlay. 6242 + getCenter: function() { 6243 + return this._bounds.getCenter(); 6244 + } 6245 + }); 6246 + var imageOverlay = function(url, bounds, options) { 6247 + return new ImageOverlay(url, bounds, options); 6248 + }; 6249 + var VideoOverlay = ImageOverlay.extend({ 6250 + // @section 6251 + // @aka VideoOverlay options 6252 + options: { 6253 + // @option autoplay: Boolean = true 6254 + // Whether the video starts playing automatically when loaded. 6255 + // On some browsers autoplay will only work with `muted: true` 6256 + autoplay: true, 6257 + // @option loop: Boolean = true 6258 + // Whether the video will loop back to the beginning when played. 6259 + loop: true, 6260 + // @option keepAspectRatio: Boolean = true 6261 + // Whether the video will save aspect ratio after the projection. 6262 + // Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) 6263 + keepAspectRatio: true, 6264 + // @option muted: Boolean = false 6265 + // Whether the video starts on mute when loaded. 6266 + muted: false, 6267 + // @option playsInline: Boolean = true 6268 + // Mobile browsers will play the video right where it is instead of open it up in fullscreen mode. 6269 + playsInline: true 6270 + }, 6271 + _initImage: function() { 6272 + var wasElementSupplied = this._url.tagName === "VIDEO"; 6273 + var vid = this._image = wasElementSupplied ? this._url : create$1("video"); 6274 + addClass(vid, "leaflet-image-layer"); 6275 + if (this._zoomAnimated) { 6276 + addClass(vid, "leaflet-zoom-animated"); 6277 + } 6278 + if (this.options.className) { 6279 + addClass(vid, this.options.className); 6280 + } 6281 + vid.onselectstart = falseFn; 6282 + vid.onmousemove = falseFn; 6283 + vid.onloadeddata = bind(this.fire, this, "load"); 6284 + if (wasElementSupplied) { 6285 + var sourceElements = vid.getElementsByTagName("source"); 6286 + var sources = []; 6287 + for (var j = 0; j < sourceElements.length; j++) { 6288 + sources.push(sourceElements[j].src); 6289 + } 6290 + this._url = sourceElements.length > 0 ? sources : [vid.src]; 6291 + return; 6292 + } 6293 + if (!isArray(this._url)) { 6294 + this._url = [this._url]; 6295 + } 6296 + if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, "objectFit")) { 6297 + vid.style["objectFit"] = "fill"; 6298 + } 6299 + vid.autoplay = !!this.options.autoplay; 6300 + vid.loop = !!this.options.loop; 6301 + vid.muted = !!this.options.muted; 6302 + vid.playsInline = !!this.options.playsInline; 6303 + for (var i = 0; i < this._url.length; i++) { 6304 + var source = create$1("source"); 6305 + source.src = this._url[i]; 6306 + vid.appendChild(source); 6307 + } 6308 + } 6309 + // @method getElement(): HTMLVideoElement 6310 + // Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement) 6311 + // used by this overlay. 6312 + }); 6313 + function videoOverlay(video, bounds, options) { 6314 + return new VideoOverlay(video, bounds, options); 6315 + } 6316 + var SVGOverlay = ImageOverlay.extend({ 6317 + _initImage: function() { 6318 + var el = this._image = this._url; 6319 + addClass(el, "leaflet-image-layer"); 6320 + if (this._zoomAnimated) { 6321 + addClass(el, "leaflet-zoom-animated"); 6322 + } 6323 + if (this.options.className) { 6324 + addClass(el, this.options.className); 6325 + } 6326 + el.onselectstart = falseFn; 6327 + el.onmousemove = falseFn; 6328 + } 6329 + // @method getElement(): SVGElement 6330 + // Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement) 6331 + // used by this overlay. 6332 + }); 6333 + function svgOverlay(el, bounds, options) { 6334 + return new SVGOverlay(el, bounds, options); 6335 + } 6336 + var DivOverlay = Layer.extend({ 6337 + // @section 6338 + // @aka DivOverlay options 6339 + options: { 6340 + // @option interactive: Boolean = false 6341 + // If true, the popup/tooltip will listen to the mouse events. 6342 + interactive: false, 6343 + // @option offset: Point = Point(0, 0) 6344 + // The offset of the overlay position. 6345 + offset: [0, 0], 6346 + // @option className: String = '' 6347 + // A custom CSS class name to assign to the overlay. 6348 + className: "", 6349 + // @option pane: String = undefined 6350 + // `Map pane` where the overlay will be added. 6351 + pane: void 0, 6352 + // @option content: String|HTMLElement|Function = '' 6353 + // Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be 6354 + // passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay. 6355 + content: "" 6356 + }, 6357 + initialize: function(options, source) { 6358 + if (options && (options instanceof LatLng || isArray(options))) { 6359 + this._latlng = toLatLng(options); 6360 + setOptions(this, source); 6361 + } else { 6362 + setOptions(this, options); 6363 + this._source = source; 6364 + } 6365 + if (this.options.content) { 6366 + this._content = this.options.content; 6367 + } 6368 + }, 6369 + // @method openOn(map: Map): this 6370 + // Adds the overlay to the map. 6371 + // Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`. 6372 + openOn: function(map2) { 6373 + map2 = arguments.length ? map2 : this._source._map; 6374 + if (!map2.hasLayer(this)) { 6375 + map2.addLayer(this); 6376 + } 6377 + return this; 6378 + }, 6379 + // @method close(): this 6380 + // Closes the overlay. 6381 + // Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)` 6382 + // and `layer.closePopup()`/`.closeTooltip()`. 6383 + close: function() { 6384 + if (this._map) { 6385 + this._map.removeLayer(this); 6386 + } 6387 + return this; 6388 + }, 6389 + // @method toggle(layer?: Layer): this 6390 + // Opens or closes the overlay bound to layer depending on its current state. 6391 + // Argument may be omitted only for overlay bound to layer. 6392 + // Alternative to `layer.togglePopup()`/`.toggleTooltip()`. 6393 + toggle: function(layer) { 6394 + if (this._map) { 6395 + this.close(); 6396 + } else { 6397 + if (arguments.length) { 6398 + this._source = layer; 6399 + } else { 6400 + layer = this._source; 6401 + } 6402 + this._prepareOpen(); 6403 + this.openOn(layer._map); 6404 + } 6405 + return this; 6406 + }, 6407 + onAdd: function(map2) { 6408 + this._zoomAnimated = map2._zoomAnimated; 6409 + if (!this._container) { 6410 + this._initLayout(); 6411 + } 6412 + if (map2._fadeAnimated) { 6413 + setOpacity(this._container, 0); 6414 + } 6415 + clearTimeout(this._removeTimeout); 6416 + this.getPane().appendChild(this._container); 6417 + this.update(); 6418 + if (map2._fadeAnimated) { 6419 + setOpacity(this._container, 1); 6420 + } 6421 + this.bringToFront(); 6422 + if (this.options.interactive) { 6423 + addClass(this._container, "leaflet-interactive"); 6424 + this.addInteractiveTarget(this._container); 6425 + } 6426 + }, 6427 + onRemove: function(map2) { 6428 + if (map2._fadeAnimated) { 6429 + setOpacity(this._container, 0); 6430 + this._removeTimeout = setTimeout(bind(remove, void 0, this._container), 200); 6431 + } else { 6432 + remove(this._container); 6433 + } 6434 + if (this.options.interactive) { 6435 + removeClass(this._container, "leaflet-interactive"); 6436 + this.removeInteractiveTarget(this._container); 6437 + } 6438 + }, 6439 + // @namespace DivOverlay 6440 + // @method getLatLng: LatLng 6441 + // Returns the geographical point of the overlay. 6442 + getLatLng: function() { 6443 + return this._latlng; 6444 + }, 6445 + // @method setLatLng(latlng: LatLng): this 6446 + // Sets the geographical point where the overlay will open. 6447 + setLatLng: function(latlng) { 6448 + this._latlng = toLatLng(latlng); 6449 + if (this._map) { 6450 + this._updatePosition(); 6451 + this._adjustPan(); 6452 + } 6453 + return this; 6454 + }, 6455 + // @method getContent: String|HTMLElement 6456 + // Returns the content of the overlay. 6457 + getContent: function() { 6458 + return this._content; 6459 + }, 6460 + // @method setContent(htmlContent: String|HTMLElement|Function): this 6461 + // Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function. 6462 + // The function should return a `String` or `HTMLElement` to be used in the overlay. 6463 + setContent: function(content) { 6464 + this._content = content; 6465 + this.update(); 6466 + return this; 6467 + }, 6468 + // @method getElement: String|HTMLElement 6469 + // Returns the HTML container of the overlay. 6470 + getElement: function() { 6471 + return this._container; 6472 + }, 6473 + // @method update: null 6474 + // Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded. 6475 + update: function() { 6476 + if (!this._map) { 6477 + return; 6478 + } 6479 + this._container.style.visibility = "hidden"; 6480 + this._updateContent(); 6481 + this._updateLayout(); 6482 + this._updatePosition(); 6483 + this._container.style.visibility = ""; 6484 + this._adjustPan(); 6485 + }, 6486 + getEvents: function() { 6487 + var events = { 6488 + zoom: this._updatePosition, 6489 + viewreset: this._updatePosition 6490 + }; 6491 + if (this._zoomAnimated) { 6492 + events.zoomanim = this._animateZoom; 6493 + } 6494 + return events; 6495 + }, 6496 + // @method isOpen: Boolean 6497 + // Returns `true` when the overlay is visible on the map. 6498 + isOpen: function() { 6499 + return !!this._map && this._map.hasLayer(this); 6500 + }, 6501 + // @method bringToFront: this 6502 + // Brings this overlay in front of other overlays (in the same map pane). 6503 + bringToFront: function() { 6504 + if (this._map) { 6505 + toFront(this._container); 6506 + } 6507 + return this; 6508 + }, 6509 + // @method bringToBack: this 6510 + // Brings this overlay to the back of other overlays (in the same map pane). 6511 + bringToBack: function() { 6512 + if (this._map) { 6513 + toBack(this._container); 6514 + } 6515 + return this; 6516 + }, 6517 + // prepare bound overlay to open: update latlng pos / content source (for FeatureGroup) 6518 + _prepareOpen: function(latlng) { 6519 + var source = this._source; 6520 + if (!source._map) { 6521 + return false; 6522 + } 6523 + if (source instanceof FeatureGroup) { 6524 + source = null; 6525 + var layers2 = this._source._layers; 6526 + for (var id in layers2) { 6527 + if (layers2[id]._map) { 6528 + source = layers2[id]; 6529 + break; 6530 + } 6531 + } 6532 + if (!source) { 6533 + return false; 6534 + } 6535 + this._source = source; 6536 + } 6537 + if (!latlng) { 6538 + if (source.getCenter) { 6539 + latlng = source.getCenter(); 6540 + } else if (source.getLatLng) { 6541 + latlng = source.getLatLng(); 6542 + } else if (source.getBounds) { 6543 + latlng = source.getBounds().getCenter(); 6544 + } else { 6545 + throw new Error("Unable to get source layer LatLng."); 6546 + } 6547 + } 6548 + this.setLatLng(latlng); 6549 + if (this._map) { 6550 + this.update(); 6551 + } 6552 + return true; 6553 + }, 6554 + _updateContent: function() { 6555 + if (!this._content) { 6556 + return; 6557 + } 6558 + var node = this._contentNode; 6559 + var content = typeof this._content === "function" ? this._content(this._source || this) : this._content; 6560 + if (typeof content === "string") { 6561 + node.innerHTML = content; 6562 + } else { 6563 + while (node.hasChildNodes()) { 6564 + node.removeChild(node.firstChild); 6565 + } 6566 + node.appendChild(content); 6567 + } 6568 + this.fire("contentupdate"); 6569 + }, 6570 + _updatePosition: function() { 6571 + if (!this._map) { 6572 + return; 6573 + } 6574 + var pos = this._map.latLngToLayerPoint(this._latlng), offset = toPoint(this.options.offset), anchor = this._getAnchor(); 6575 + if (this._zoomAnimated) { 6576 + setPosition(this._container, pos.add(anchor)); 6577 + } else { 6578 + offset = offset.add(pos).add(anchor); 6579 + } 6580 + var bottom = this._containerBottom = -offset.y, left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x; 6581 + this._container.style.bottom = bottom + "px"; 6582 + this._container.style.left = left + "px"; 6583 + }, 6584 + _getAnchor: function() { 6585 + return [0, 0]; 6586 + } 6587 + }); 6588 + Map2.include({ 6589 + _initOverlay: function(OverlayClass, content, latlng, options) { 6590 + var overlay = content; 6591 + if (!(overlay instanceof OverlayClass)) { 6592 + overlay = new OverlayClass(options).setContent(content); 6593 + } 6594 + if (latlng) { 6595 + overlay.setLatLng(latlng); 6596 + } 6597 + return overlay; 6598 + } 6599 + }); 6600 + Layer.include({ 6601 + _initOverlay: function(OverlayClass, old, content, options) { 6602 + var overlay = content; 6603 + if (overlay instanceof OverlayClass) { 6604 + setOptions(overlay, options); 6605 + overlay._source = this; 6606 + } else { 6607 + overlay = old && !options ? old : new OverlayClass(options, this); 6608 + overlay.setContent(content); 6609 + } 6610 + return overlay; 6611 + } 6612 + }); 6613 + var Popup = DivOverlay.extend({ 6614 + // @section 6615 + // @aka Popup options 6616 + options: { 6617 + // @option pane: String = 'popupPane' 6618 + // `Map pane` where the popup will be added. 6619 + pane: "popupPane", 6620 + // @option offset: Point = Point(0, 7) 6621 + // The offset of the popup position. 6622 + offset: [0, 7], 6623 + // @option maxWidth: Number = 300 6624 + // Max width of the popup, in pixels. 6625 + maxWidth: 300, 6626 + // @option minWidth: Number = 50 6627 + // Min width of the popup, in pixels. 6628 + minWidth: 50, 6629 + // @option maxHeight: Number = null 6630 + // If set, creates a scrollable container of the given height 6631 + // inside a popup if its content exceeds it. 6632 + // The scrollable container can be styled using the 6633 + // `leaflet-popup-scrolled` CSS class selector. 6634 + maxHeight: null, 6635 + // @option autoPan: Boolean = true 6636 + // Set it to `false` if you don't want the map to do panning animation 6637 + // to fit the opened popup. 6638 + autoPan: true, 6639 + // @option autoPanPaddingTopLeft: Point = null 6640 + // The margin between the popup and the top left corner of the map 6641 + // view after autopanning was performed. 6642 + autoPanPaddingTopLeft: null, 6643 + // @option autoPanPaddingBottomRight: Point = null 6644 + // The margin between the popup and the bottom right corner of the map 6645 + // view after autopanning was performed. 6646 + autoPanPaddingBottomRight: null, 6647 + // @option autoPanPadding: Point = Point(5, 5) 6648 + // Equivalent of setting both top left and bottom right autopan padding to the same value. 6649 + autoPanPadding: [5, 5], 6650 + // @option keepInView: Boolean = false 6651 + // Set it to `true` if you want to prevent users from panning the popup 6652 + // off of the screen while it is open. 6653 + keepInView: false, 6654 + // @option closeButton: Boolean = true 6655 + // Controls the presence of a close button in the popup. 6656 + closeButton: true, 6657 + // @option autoClose: Boolean = true 6658 + // Set it to `false` if you want to override the default behavior of 6659 + // the popup closing when another popup is opened. 6660 + autoClose: true, 6661 + // @option closeOnEscapeKey: Boolean = true 6662 + // Set it to `false` if you want to override the default behavior of 6663 + // the ESC key for closing of the popup. 6664 + closeOnEscapeKey: true, 6665 + // @option closeOnClick: Boolean = * 6666 + // Set it if you want to override the default behavior of the popup closing when user clicks 6667 + // on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option. 6668 + // @option className: String = '' 6669 + // A custom CSS class name to assign to the popup. 6670 + className: "" 6671 + }, 6672 + // @namespace Popup 6673 + // @method openOn(map: Map): this 6674 + // Alternative to `map.openPopup(popup)`. 6675 + // Adds the popup to the map and closes the previous one. 6676 + openOn: function(map2) { 6677 + map2 = arguments.length ? map2 : this._source._map; 6678 + if (!map2.hasLayer(this) && map2._popup && map2._popup.options.autoClose) { 6679 + map2.removeLayer(map2._popup); 6680 + } 6681 + map2._popup = this; 6682 + return DivOverlay.prototype.openOn.call(this, map2); 6683 + }, 6684 + onAdd: function(map2) { 6685 + DivOverlay.prototype.onAdd.call(this, map2); 6686 + map2.fire("popupopen", { popup: this }); 6687 + if (this._source) { 6688 + this._source.fire("popupopen", { popup: this }, true); 6689 + if (!(this._source instanceof Path)) { 6690 + this._source.on("preclick", stopPropagation); 6691 + } 6692 + } 6693 + }, 6694 + onRemove: function(map2) { 6695 + DivOverlay.prototype.onRemove.call(this, map2); 6696 + map2.fire("popupclose", { popup: this }); 6697 + if (this._source) { 6698 + this._source.fire("popupclose", { popup: this }, true); 6699 + if (!(this._source instanceof Path)) { 6700 + this._source.off("preclick", stopPropagation); 6701 + } 6702 + } 6703 + }, 6704 + getEvents: function() { 6705 + var events = DivOverlay.prototype.getEvents.call(this); 6706 + if (this.options.closeOnClick !== void 0 ? this.options.closeOnClick : this._map.options.closePopupOnClick) { 6707 + events.preclick = this.close; 6708 + } 6709 + if (this.options.keepInView) { 6710 + events.moveend = this._adjustPan; 6711 + } 6712 + return events; 6713 + }, 6714 + _initLayout: function() { 6715 + var prefix = "leaflet-popup", container = this._container = create$1( 6716 + "div", 6717 + prefix + " " + (this.options.className || "") + " leaflet-zoom-animated" 6718 + ); 6719 + var wrapper = this._wrapper = create$1("div", prefix + "-content-wrapper", container); 6720 + this._contentNode = create$1("div", prefix + "-content", wrapper); 6721 + disableClickPropagation(container); 6722 + disableScrollPropagation(this._contentNode); 6723 + on(container, "contextmenu", stopPropagation); 6724 + this._tipContainer = create$1("div", prefix + "-tip-container", container); 6725 + this._tip = create$1("div", prefix + "-tip", this._tipContainer); 6726 + if (this.options.closeButton) { 6727 + var closeButton = this._closeButton = create$1("a", prefix + "-close-button", container); 6728 + closeButton.setAttribute("role", "button"); 6729 + closeButton.setAttribute("aria-label", "Close popup"); 6730 + closeButton.href = "#close"; 6731 + closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>'; 6732 + on(closeButton, "click", function(ev) { 6733 + preventDefault(ev); 6734 + this.close(); 6735 + }, this); 6736 + } 6737 + }, 6738 + _updateLayout: function() { 6739 + var container = this._contentNode, style2 = container.style; 6740 + style2.width = ""; 6741 + style2.whiteSpace = "nowrap"; 6742 + var width = container.offsetWidth; 6743 + width = Math.min(width, this.options.maxWidth); 6744 + width = Math.max(width, this.options.minWidth); 6745 + style2.width = width + 1 + "px"; 6746 + style2.whiteSpace = ""; 6747 + style2.height = ""; 6748 + var height = container.offsetHeight, maxHeight = this.options.maxHeight, scrolledClass = "leaflet-popup-scrolled"; 6749 + if (maxHeight && height > maxHeight) { 6750 + style2.height = maxHeight + "px"; 6751 + addClass(container, scrolledClass); 6752 + } else { 6753 + removeClass(container, scrolledClass); 6754 + } 6755 + this._containerWidth = this._container.offsetWidth; 6756 + }, 6757 + _animateZoom: function(e) { 6758 + var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center), anchor = this._getAnchor(); 6759 + setPosition(this._container, pos.add(anchor)); 6760 + }, 6761 + _adjustPan: function() { 6762 + if (!this.options.autoPan) { 6763 + return; 6764 + } 6765 + if (this._map._panAnim) { 6766 + this._map._panAnim.stop(); 6767 + } 6768 + if (this._autopanning) { 6769 + this._autopanning = false; 6770 + return; 6771 + } 6772 + var map2 = this._map, marginBottom = parseInt(getStyle(this._container, "marginBottom"), 10) || 0, containerHeight = this._container.offsetHeight + marginBottom, containerWidth = this._containerWidth, layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom); 6773 + layerPos._add(getPosition(this._container)); 6774 + var containerPos = map2.layerPointToContainerPoint(layerPos), padding = toPoint(this.options.autoPanPadding), paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding), paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding), size = map2.getSize(), dx = 0, dy = 0; 6775 + if (containerPos.x + containerWidth + paddingBR.x > size.x) { 6776 + dx = containerPos.x + containerWidth - size.x + paddingBR.x; 6777 + } 6778 + if (containerPos.x - dx - paddingTL.x < 0) { 6779 + dx = containerPos.x - paddingTL.x; 6780 + } 6781 + if (containerPos.y + containerHeight + paddingBR.y > size.y) { 6782 + dy = containerPos.y + containerHeight - size.y + paddingBR.y; 6783 + } 6784 + if (containerPos.y - dy - paddingTL.y < 0) { 6785 + dy = containerPos.y - paddingTL.y; 6786 + } 6787 + if (dx || dy) { 6788 + if (this.options.keepInView) { 6789 + this._autopanning = true; 6790 + } 6791 + map2.fire("autopanstart").panBy([dx, dy]); 6792 + } 6793 + }, 6794 + _getAnchor: function() { 6795 + return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]); 6796 + } 6797 + }); 6798 + var popup = function(options, source) { 6799 + return new Popup(options, source); 6800 + }; 6801 + Map2.mergeOptions({ 6802 + closePopupOnClick: true 6803 + }); 6804 + Map2.include({ 6805 + // @method openPopup(popup: Popup): this 6806 + // Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability). 6807 + // @alternative 6808 + // @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this 6809 + // Creates a popup with the specified content and options and opens it in the given point on a map. 6810 + openPopup: function(popup2, latlng, options) { 6811 + this._initOverlay(Popup, popup2, latlng, options).openOn(this); 6812 + return this; 6813 + }, 6814 + // @method closePopup(popup?: Popup): this 6815 + // Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one). 6816 + closePopup: function(popup2) { 6817 + popup2 = arguments.length ? popup2 : this._popup; 6818 + if (popup2) { 6819 + popup2.close(); 6820 + } 6821 + return this; 6822 + } 6823 + }); 6824 + Layer.include({ 6825 + // @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this 6826 + // Binds a popup to the layer with the passed `content` and sets up the 6827 + // necessary event listeners. If a `Function` is passed it will receive 6828 + // the layer as the first argument and should return a `String` or `HTMLElement`. 6829 + bindPopup: function(content, options) { 6830 + this._popup = this._initOverlay(Popup, this._popup, content, options); 6831 + if (!this._popupHandlersAdded) { 6832 + this.on({ 6833 + click: this._openPopup, 6834 + keypress: this._onKeyPress, 6835 + remove: this.closePopup, 6836 + move: this._movePopup 6837 + }); 6838 + this._popupHandlersAdded = true; 6839 + } 6840 + return this; 6841 + }, 6842 + // @method unbindPopup(): this 6843 + // Removes the popup previously bound with `bindPopup`. 6844 + unbindPopup: function() { 6845 + if (this._popup) { 6846 + this.off({ 6847 + click: this._openPopup, 6848 + keypress: this._onKeyPress, 6849 + remove: this.closePopup, 6850 + move: this._movePopup 6851 + }); 6852 + this._popupHandlersAdded = false; 6853 + this._popup = null; 6854 + } 6855 + return this; 6856 + }, 6857 + // @method openPopup(latlng?: LatLng): this 6858 + // Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed. 6859 + openPopup: function(latlng) { 6860 + if (this._popup) { 6861 + if (!(this instanceof FeatureGroup)) { 6862 + this._popup._source = this; 6863 + } 6864 + if (this._popup._prepareOpen(latlng || this._latlng)) { 6865 + this._popup.openOn(this._map); 6866 + } 6867 + } 6868 + return this; 6869 + }, 6870 + // @method closePopup(): this 6871 + // Closes the popup bound to this layer if it is open. 6872 + closePopup: function() { 6873 + if (this._popup) { 6874 + this._popup.close(); 6875 + } 6876 + return this; 6877 + }, 6878 + // @method togglePopup(): this 6879 + // Opens or closes the popup bound to this layer depending on its current state. 6880 + togglePopup: function() { 6881 + if (this._popup) { 6882 + this._popup.toggle(this); 6883 + } 6884 + return this; 6885 + }, 6886 + // @method isPopupOpen(): boolean 6887 + // Returns `true` if the popup bound to this layer is currently open. 6888 + isPopupOpen: function() { 6889 + return this._popup ? this._popup.isOpen() : false; 6890 + }, 6891 + // @method setPopupContent(content: String|HTMLElement|Popup): this 6892 + // Sets the content of the popup bound to this layer. 6893 + setPopupContent: function(content) { 6894 + if (this._popup) { 6895 + this._popup.setContent(content); 6896 + } 6897 + return this; 6898 + }, 6899 + // @method getPopup(): Popup 6900 + // Returns the popup bound to this layer. 6901 + getPopup: function() { 6902 + return this._popup; 6903 + }, 6904 + _openPopup: function(e) { 6905 + if (!this._popup || !this._map) { 6906 + return; 6907 + } 6908 + stop(e); 6909 + var target = e.layer || e.target; 6910 + if (this._popup._source === target && !(target instanceof Path)) { 6911 + if (this._map.hasLayer(this._popup)) { 6912 + this.closePopup(); 6913 + } else { 6914 + this.openPopup(e.latlng); 6915 + } 6916 + return; 6917 + } 6918 + this._popup._source = target; 6919 + this.openPopup(e.latlng); 6920 + }, 6921 + _movePopup: function(e) { 6922 + this._popup.setLatLng(e.latlng); 6923 + }, 6924 + _onKeyPress: function(e) { 6925 + if (e.originalEvent.keyCode === 13) { 6926 + this._openPopup(e); 6927 + } 6928 + } 6929 + }); 6930 + var Tooltip = DivOverlay.extend({ 6931 + // @section 6932 + // @aka Tooltip options 6933 + options: { 6934 + // @option pane: String = 'tooltipPane' 6935 + // `Map pane` where the tooltip will be added. 6936 + pane: "tooltipPane", 6937 + // @option offset: Point = Point(0, 0) 6938 + // Optional offset of the tooltip position. 6939 + offset: [0, 0], 6940 + // @option direction: String = 'auto' 6941 + // Direction where to open the tooltip. Possible values are: `right`, `left`, 6942 + // `top`, `bottom`, `center`, `auto`. 6943 + // `auto` will dynamically switch between `right` and `left` according to the tooltip 6944 + // position on the map. 6945 + direction: "auto", 6946 + // @option permanent: Boolean = false 6947 + // Whether to open the tooltip permanently or only on mouseover. 6948 + permanent: false, 6949 + // @option sticky: Boolean = false 6950 + // If true, the tooltip will follow the mouse instead of being fixed at the feature center. 6951 + sticky: false, 6952 + // @option opacity: Number = 0.9 6953 + // Tooltip container opacity. 6954 + opacity: 0.9 6955 + }, 6956 + onAdd: function(map2) { 6957 + DivOverlay.prototype.onAdd.call(this, map2); 6958 + this.setOpacity(this.options.opacity); 6959 + map2.fire("tooltipopen", { tooltip: this }); 6960 + if (this._source) { 6961 + this.addEventParent(this._source); 6962 + this._source.fire("tooltipopen", { tooltip: this }, true); 6963 + } 6964 + }, 6965 + onRemove: function(map2) { 6966 + DivOverlay.prototype.onRemove.call(this, map2); 6967 + map2.fire("tooltipclose", { tooltip: this }); 6968 + if (this._source) { 6969 + this.removeEventParent(this._source); 6970 + this._source.fire("tooltipclose", { tooltip: this }, true); 6971 + } 6972 + }, 6973 + getEvents: function() { 6974 + var events = DivOverlay.prototype.getEvents.call(this); 6975 + if (!this.options.permanent) { 6976 + events.preclick = this.close; 6977 + } 6978 + return events; 6979 + }, 6980 + _initLayout: function() { 6981 + var prefix = "leaflet-tooltip", className = prefix + " " + (this.options.className || "") + " leaflet-zoom-" + (this._zoomAnimated ? "animated" : "hide"); 6982 + this._contentNode = this._container = create$1("div", className); 6983 + this._container.setAttribute("role", "tooltip"); 6984 + this._container.setAttribute("id", "leaflet-tooltip-" + stamp(this)); 6985 + }, 6986 + _updateLayout: function() { 6987 + }, 6988 + _adjustPan: function() { 6989 + }, 6990 + _setPosition: function(pos) { 6991 + var subX, subY, map2 = this._map, container = this._container, centerPoint = map2.latLngToContainerPoint(map2.getCenter()), tooltipPoint = map2.layerPointToContainerPoint(pos), direction = this.options.direction, tooltipWidth = container.offsetWidth, tooltipHeight = container.offsetHeight, offset = toPoint(this.options.offset), anchor = this._getAnchor(); 6992 + if (direction === "top") { 6993 + subX = tooltipWidth / 2; 6994 + subY = tooltipHeight; 6995 + } else if (direction === "bottom") { 6996 + subX = tooltipWidth / 2; 6997 + subY = 0; 6998 + } else if (direction === "center") { 6999 + subX = tooltipWidth / 2; 7000 + subY = tooltipHeight / 2; 7001 + } else if (direction === "right") { 7002 + subX = 0; 7003 + subY = tooltipHeight / 2; 7004 + } else if (direction === "left") { 7005 + subX = tooltipWidth; 7006 + subY = tooltipHeight / 2; 7007 + } else if (tooltipPoint.x < centerPoint.x) { 7008 + direction = "right"; 7009 + subX = 0; 7010 + subY = tooltipHeight / 2; 7011 + } else { 7012 + direction = "left"; 7013 + subX = tooltipWidth + (offset.x + anchor.x) * 2; 7014 + subY = tooltipHeight / 2; 7015 + } 7016 + pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor); 7017 + removeClass(container, "leaflet-tooltip-right"); 7018 + removeClass(container, "leaflet-tooltip-left"); 7019 + removeClass(container, "leaflet-tooltip-top"); 7020 + removeClass(container, "leaflet-tooltip-bottom"); 7021 + addClass(container, "leaflet-tooltip-" + direction); 7022 + setPosition(container, pos); 7023 + }, 7024 + _updatePosition: function() { 7025 + var pos = this._map.latLngToLayerPoint(this._latlng); 7026 + this._setPosition(pos); 7027 + }, 7028 + setOpacity: function(opacity) { 7029 + this.options.opacity = opacity; 7030 + if (this._container) { 7031 + setOpacity(this._container, opacity); 7032 + } 7033 + }, 7034 + _animateZoom: function(e) { 7035 + var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center); 7036 + this._setPosition(pos); 7037 + }, 7038 + _getAnchor: function() { 7039 + return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]); 7040 + } 7041 + }); 7042 + var tooltip = function(options, source) { 7043 + return new Tooltip(options, source); 7044 + }; 7045 + Map2.include({ 7046 + // @method openTooltip(tooltip: Tooltip): this 7047 + // Opens the specified tooltip. 7048 + // @alternative 7049 + // @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this 7050 + // Creates a tooltip with the specified content and options and open it. 7051 + openTooltip: function(tooltip2, latlng, options) { 7052 + this._initOverlay(Tooltip, tooltip2, latlng, options).openOn(this); 7053 + return this; 7054 + }, 7055 + // @method closeTooltip(tooltip: Tooltip): this 7056 + // Closes the tooltip given as parameter. 7057 + closeTooltip: function(tooltip2) { 7058 + tooltip2.close(); 7059 + return this; 7060 + } 7061 + }); 7062 + Layer.include({ 7063 + // @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this 7064 + // Binds a tooltip to the layer with the passed `content` and sets up the 7065 + // necessary event listeners. If a `Function` is passed it will receive 7066 + // the layer as the first argument and should return a `String` or `HTMLElement`. 7067 + bindTooltip: function(content, options) { 7068 + if (this._tooltip && this.isTooltipOpen()) { 7069 + this.unbindTooltip(); 7070 + } 7071 + this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options); 7072 + this._initTooltipInteractions(); 7073 + if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) { 7074 + this.openTooltip(); 7075 + } 7076 + return this; 7077 + }, 7078 + // @method unbindTooltip(): this 7079 + // Removes the tooltip previously bound with `bindTooltip`. 7080 + unbindTooltip: function() { 7081 + if (this._tooltip) { 7082 + this._initTooltipInteractions(true); 7083 + this.closeTooltip(); 7084 + this._tooltip = null; 7085 + } 7086 + return this; 7087 + }, 7088 + _initTooltipInteractions: function(remove2) { 7089 + if (!remove2 && this._tooltipHandlersAdded) { 7090 + return; 7091 + } 7092 + var onOff = remove2 ? "off" : "on", events = { 7093 + remove: this.closeTooltip, 7094 + move: this._moveTooltip 7095 + }; 7096 + if (!this._tooltip.options.permanent) { 7097 + events.mouseover = this._openTooltip; 7098 + events.mouseout = this.closeTooltip; 7099 + events.click = this._openTooltip; 7100 + if (this._map) { 7101 + this._addFocusListeners(); 7102 + } else { 7103 + events.add = this._addFocusListeners; 7104 + } 7105 + } else { 7106 + events.add = this._openTooltip; 7107 + } 7108 + if (this._tooltip.options.sticky) { 7109 + events.mousemove = this._moveTooltip; 7110 + } 7111 + this[onOff](events); 7112 + this._tooltipHandlersAdded = !remove2; 7113 + }, 7114 + // @method openTooltip(latlng?: LatLng): this 7115 + // Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed. 7116 + openTooltip: function(latlng) { 7117 + if (this._tooltip) { 7118 + if (!(this instanceof FeatureGroup)) { 7119 + this._tooltip._source = this; 7120 + } 7121 + if (this._tooltip._prepareOpen(latlng)) { 7122 + this._tooltip.openOn(this._map); 7123 + if (this.getElement) { 7124 + this._setAriaDescribedByOnLayer(this); 7125 + } else if (this.eachLayer) { 7126 + this.eachLayer(this._setAriaDescribedByOnLayer, this); 7127 + } 7128 + } 7129 + } 7130 + return this; 7131 + }, 7132 + // @method closeTooltip(): this 7133 + // Closes the tooltip bound to this layer if it is open. 7134 + closeTooltip: function() { 7135 + if (this._tooltip) { 7136 + return this._tooltip.close(); 7137 + } 7138 + }, 7139 + // @method toggleTooltip(): this 7140 + // Opens or closes the tooltip bound to this layer depending on its current state. 7141 + toggleTooltip: function() { 7142 + if (this._tooltip) { 7143 + this._tooltip.toggle(this); 7144 + } 7145 + return this; 7146 + }, 7147 + // @method isTooltipOpen(): boolean 7148 + // Returns `true` if the tooltip bound to this layer is currently open. 7149 + isTooltipOpen: function() { 7150 + return this._tooltip.isOpen(); 7151 + }, 7152 + // @method setTooltipContent(content: String|HTMLElement|Tooltip): this 7153 + // Sets the content of the tooltip bound to this layer. 7154 + setTooltipContent: function(content) { 7155 + if (this._tooltip) { 7156 + this._tooltip.setContent(content); 7157 + } 7158 + return this; 7159 + }, 7160 + // @method getTooltip(): Tooltip 7161 + // Returns the tooltip bound to this layer. 7162 + getTooltip: function() { 7163 + return this._tooltip; 7164 + }, 7165 + _addFocusListeners: function() { 7166 + if (this.getElement) { 7167 + this._addFocusListenersOnLayer(this); 7168 + } else if (this.eachLayer) { 7169 + this.eachLayer(this._addFocusListenersOnLayer, this); 7170 + } 7171 + }, 7172 + _addFocusListenersOnLayer: function(layer) { 7173 + var el = typeof layer.getElement === "function" && layer.getElement(); 7174 + if (el) { 7175 + on(el, "focus", function() { 7176 + this._tooltip._source = layer; 7177 + this.openTooltip(); 7178 + }, this); 7179 + on(el, "blur", this.closeTooltip, this); 7180 + } 7181 + }, 7182 + _setAriaDescribedByOnLayer: function(layer) { 7183 + var el = typeof layer.getElement === "function" && layer.getElement(); 7184 + if (el) { 7185 + el.setAttribute("aria-describedby", this._tooltip._container.id); 7186 + } 7187 + }, 7188 + _openTooltip: function(e) { 7189 + if (!this._tooltip || !this._map) { 7190 + return; 7191 + } 7192 + if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) { 7193 + this._openOnceFlag = true; 7194 + var that = this; 7195 + this._map.once("moveend", function() { 7196 + that._openOnceFlag = false; 7197 + that._openTooltip(e); 7198 + }); 7199 + return; 7200 + } 7201 + this._tooltip._source = e.layer || e.target; 7202 + this.openTooltip(this._tooltip.options.sticky ? e.latlng : void 0); 7203 + }, 7204 + _moveTooltip: function(e) { 7205 + var latlng = e.latlng, containerPoint, layerPoint; 7206 + if (this._tooltip.options.sticky && e.originalEvent) { 7207 + containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent); 7208 + layerPoint = this._map.containerPointToLayerPoint(containerPoint); 7209 + latlng = this._map.layerPointToLatLng(layerPoint); 7210 + } 7211 + this._tooltip.setLatLng(latlng); 7212 + } 7213 + }); 7214 + var DivIcon = Icon.extend({ 7215 + options: { 7216 + // @section 7217 + // @aka DivIcon options 7218 + iconSize: [12, 12], 7219 + // also can be set through CSS 7220 + // iconAnchor: (Point), 7221 + // popupAnchor: (Point), 7222 + // @option html: String|HTMLElement = '' 7223 + // Custom HTML code to put inside the div element, empty by default. Alternatively, 7224 + // an instance of `HTMLElement`. 7225 + html: false, 7226 + // @option bgPos: Point = [0, 0] 7227 + // Optional relative position of the background, in pixels 7228 + bgPos: null, 7229 + className: "leaflet-div-icon" 7230 + }, 7231 + createIcon: function(oldIcon) { 7232 + var div = oldIcon && oldIcon.tagName === "DIV" ? oldIcon : document.createElement("div"), options = this.options; 7233 + if (options.html instanceof Element) { 7234 + empty(div); 7235 + div.appendChild(options.html); 7236 + } else { 7237 + div.innerHTML = options.html !== false ? options.html : ""; 7238 + } 7239 + if (options.bgPos) { 7240 + var bgPos = toPoint(options.bgPos); 7241 + div.style.backgroundPosition = -bgPos.x + "px " + -bgPos.y + "px"; 7242 + } 7243 + this._setIconStyles(div, "icon"); 7244 + return div; 7245 + }, 7246 + createShadow: function() { 7247 + return null; 7248 + } 7249 + }); 7250 + function divIcon2(options) { 7251 + return new DivIcon(options); 7252 + } 7253 + Icon.Default = IconDefault; 7254 + var GridLayer = Layer.extend({ 7255 + // @section 7256 + // @aka GridLayer options 7257 + options: { 7258 + // @option tileSize: Number|Point = 256 7259 + // Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise. 7260 + tileSize: 256, 7261 + // @option opacity: Number = 1.0 7262 + // Opacity of the tiles. Can be used in the `createTile()` function. 7263 + opacity: 1, 7264 + // @option updateWhenIdle: Boolean = (depends) 7265 + // Load new tiles only when panning ends. 7266 + // `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation. 7267 + // `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the 7268 + // [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers. 7269 + updateWhenIdle: Browser.mobile, 7270 + // @option updateWhenZooming: Boolean = true 7271 + // By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends. 7272 + updateWhenZooming: true, 7273 + // @option updateInterval: Number = 200 7274 + // Tiles will not update more than once every `updateInterval` milliseconds when panning. 7275 + updateInterval: 200, 7276 + // @option zIndex: Number = 1 7277 + // The explicit zIndex of the tile layer. 7278 + zIndex: 1, 7279 + // @option bounds: LatLngBounds = undefined 7280 + // If set, tiles will only be loaded inside the set `LatLngBounds`. 7281 + bounds: null, 7282 + // @option minZoom: Number = 0 7283 + // The minimum zoom level down to which this layer will be displayed (inclusive). 7284 + minZoom: 0, 7285 + // @option maxZoom: Number = undefined 7286 + // The maximum zoom level up to which this layer will be displayed (inclusive). 7287 + maxZoom: void 0, 7288 + // @option maxNativeZoom: Number = undefined 7289 + // Maximum zoom number the tile source has available. If it is specified, 7290 + // the tiles on all zoom levels higher than `maxNativeZoom` will be loaded 7291 + // from `maxNativeZoom` level and auto-scaled. 7292 + maxNativeZoom: void 0, 7293 + // @option minNativeZoom: Number = undefined 7294 + // Minimum zoom number the tile source has available. If it is specified, 7295 + // the tiles on all zoom levels lower than `minNativeZoom` will be loaded 7296 + // from `minNativeZoom` level and auto-scaled. 7297 + minNativeZoom: void 0, 7298 + // @option noWrap: Boolean = false 7299 + // Whether the layer is wrapped around the antimeridian. If `true`, the 7300 + // GridLayer will only be displayed once at low zoom levels. Has no 7301 + // effect when the [map CRS](#map-crs) doesn't wrap around. Can be used 7302 + // in combination with [`bounds`](#gridlayer-bounds) to prevent requesting 7303 + // tiles outside the CRS limits. 7304 + noWrap: false, 7305 + // @option pane: String = 'tilePane' 7306 + // `Map pane` where the grid layer will be added. 7307 + pane: "tilePane", 7308 + // @option className: String = '' 7309 + // A custom class name to assign to the tile layer. Empty by default. 7310 + className: "", 7311 + // @option keepBuffer: Number = 2 7312 + // When panning the map, keep this many rows and columns of tiles before unloading them. 7313 + keepBuffer: 2 7314 + }, 7315 + initialize: function(options) { 7316 + setOptions(this, options); 7317 + }, 7318 + onAdd: function() { 7319 + this._initContainer(); 7320 + this._levels = {}; 7321 + this._tiles = {}; 7322 + this._resetView(); 7323 + }, 7324 + beforeAdd: function(map2) { 7325 + map2._addZoomLimit(this); 7326 + }, 7327 + onRemove: function(map2) { 7328 + this._removeAllTiles(); 7329 + remove(this._container); 7330 + map2._removeZoomLimit(this); 7331 + this._container = null; 7332 + this._tileZoom = void 0; 7333 + }, 7334 + // @method bringToFront: this 7335 + // Brings the tile layer to the top of all tile layers. 7336 + bringToFront: function() { 7337 + if (this._map) { 7338 + toFront(this._container); 7339 + this._setAutoZIndex(Math.max); 7340 + } 7341 + return this; 7342 + }, 7343 + // @method bringToBack: this 7344 + // Brings the tile layer to the bottom of all tile layers. 7345 + bringToBack: function() { 7346 + if (this._map) { 7347 + toBack(this._container); 7348 + this._setAutoZIndex(Math.min); 7349 + } 7350 + return this; 7351 + }, 7352 + // @method getContainer: HTMLElement 7353 + // Returns the HTML element that contains the tiles for this layer. 7354 + getContainer: function() { 7355 + return this._container; 7356 + }, 7357 + // @method setOpacity(opacity: Number): this 7358 + // Changes the [opacity](#gridlayer-opacity) of the grid layer. 7359 + setOpacity: function(opacity) { 7360 + this.options.opacity = opacity; 7361 + this._updateOpacity(); 7362 + return this; 7363 + }, 7364 + // @method setZIndex(zIndex: Number): this 7365 + // Changes the [zIndex](#gridlayer-zindex) of the grid layer. 7366 + setZIndex: function(zIndex) { 7367 + this.options.zIndex = zIndex; 7368 + this._updateZIndex(); 7369 + return this; 7370 + }, 7371 + // @method isLoading: Boolean 7372 + // Returns `true` if any tile in the grid layer has not finished loading. 7373 + isLoading: function() { 7374 + return this._loading; 7375 + }, 7376 + // @method redraw: this 7377 + // Causes the layer to clear all the tiles and request them again. 7378 + redraw: function() { 7379 + if (this._map) { 7380 + this._removeAllTiles(); 7381 + var tileZoom = this._clampZoom(this._map.getZoom()); 7382 + if (tileZoom !== this._tileZoom) { 7383 + this._tileZoom = tileZoom; 7384 + this._updateLevels(); 7385 + } 7386 + this._update(); 7387 + } 7388 + return this; 7389 + }, 7390 + getEvents: function() { 7391 + var events = { 7392 + viewprereset: this._invalidateAll, 7393 + viewreset: this._resetView, 7394 + zoom: this._resetView, 7395 + moveend: this._onMoveEnd 7396 + }; 7397 + if (!this.options.updateWhenIdle) { 7398 + if (!this._onMove) { 7399 + this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this); 7400 + } 7401 + events.move = this._onMove; 7402 + } 7403 + if (this._zoomAnimated) { 7404 + events.zoomanim = this._animateZoom; 7405 + } 7406 + return events; 7407 + }, 7408 + // @section Extension methods 7409 + // Layers extending `GridLayer` shall reimplement the following method. 7410 + // @method createTile(coords: Object, done?: Function): HTMLElement 7411 + // Called only internally, must be overridden by classes extending `GridLayer`. 7412 + // Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback 7413 + // is specified, it must be called when the tile has finished loading and drawing. 7414 + createTile: function() { 7415 + return document.createElement("div"); 7416 + }, 7417 + // @section 7418 + // @method getTileSize: Point 7419 + // Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method. 7420 + getTileSize: function() { 7421 + var s = this.options.tileSize; 7422 + return s instanceof Point ? s : new Point(s, s); 7423 + }, 7424 + _updateZIndex: function() { 7425 + if (this._container && this.options.zIndex !== void 0 && this.options.zIndex !== null) { 7426 + this._container.style.zIndex = this.options.zIndex; 7427 + } 7428 + }, 7429 + _setAutoZIndex: function(compare) { 7430 + var layers2 = this.getPane().children, edgeZIndex = -compare(-Infinity, Infinity); 7431 + for (var i = 0, len = layers2.length, zIndex; i < len; i++) { 7432 + zIndex = layers2[i].style.zIndex; 7433 + if (layers2[i] !== this._container && zIndex) { 7434 + edgeZIndex = compare(edgeZIndex, +zIndex); 7435 + } 7436 + } 7437 + if (isFinite(edgeZIndex)) { 7438 + this.options.zIndex = edgeZIndex + compare(-1, 1); 7439 + this._updateZIndex(); 7440 + } 7441 + }, 7442 + _updateOpacity: function() { 7443 + if (!this._map) { 7444 + return; 7445 + } 7446 + if (Browser.ielt9) { 7447 + return; 7448 + } 7449 + setOpacity(this._container, this.options.opacity); 7450 + var now = +/* @__PURE__ */ new Date(), nextFrame = false, willPrune = false; 7451 + for (var key in this._tiles) { 7452 + var tile = this._tiles[key]; 7453 + if (!tile.current || !tile.loaded) { 7454 + continue; 7455 + } 7456 + var fade = Math.min(1, (now - tile.loaded) / 200); 7457 + setOpacity(tile.el, fade); 7458 + if (fade < 1) { 7459 + nextFrame = true; 7460 + } else { 7461 + if (tile.active) { 7462 + willPrune = true; 7463 + } else { 7464 + this._onOpaqueTile(tile); 7465 + } 7466 + tile.active = true; 7467 + } 7468 + } 7469 + if (willPrune && !this._noPrune) { 7470 + this._pruneTiles(); 7471 + } 7472 + if (nextFrame) { 7473 + cancelAnimFrame(this._fadeFrame); 7474 + this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 7475 + } 7476 + }, 7477 + _onOpaqueTile: falseFn, 7478 + _initContainer: function() { 7479 + if (this._container) { 7480 + return; 7481 + } 7482 + this._container = create$1("div", "leaflet-layer " + (this.options.className || "")); 7483 + this._updateZIndex(); 7484 + if (this.options.opacity < 1) { 7485 + this._updateOpacity(); 7486 + } 7487 + this.getPane().appendChild(this._container); 7488 + }, 7489 + _updateLevels: function() { 7490 + var zoom2 = this._tileZoom, maxZoom = this.options.maxZoom; 7491 + if (zoom2 === void 0) { 7492 + return void 0; 7493 + } 7494 + for (var z in this._levels) { 7495 + z = Number(z); 7496 + if (this._levels[z].el.children.length || z === zoom2) { 7497 + this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom2 - z); 7498 + this._onUpdateLevel(z); 7499 + } else { 7500 + remove(this._levels[z].el); 7501 + this._removeTilesAtZoom(z); 7502 + this._onRemoveLevel(z); 7503 + delete this._levels[z]; 7504 + } 7505 + } 7506 + var level = this._levels[zoom2], map2 = this._map; 7507 + if (!level) { 7508 + level = this._levels[zoom2] = {}; 7509 + level.el = create$1("div", "leaflet-tile-container leaflet-zoom-animated", this._container); 7510 + level.el.style.zIndex = maxZoom; 7511 + level.origin = map2.project(map2.unproject(map2.getPixelOrigin()), zoom2).round(); 7512 + level.zoom = zoom2; 7513 + this._setZoomTransform(level, map2.getCenter(), map2.getZoom()); 7514 + falseFn(level.el.offsetWidth); 7515 + this._onCreateLevel(level); 7516 + } 7517 + this._level = level; 7518 + return level; 7519 + }, 7520 + _onUpdateLevel: falseFn, 7521 + _onRemoveLevel: falseFn, 7522 + _onCreateLevel: falseFn, 7523 + _pruneTiles: function() { 7524 + if (!this._map) { 7525 + return; 7526 + } 7527 + var key, tile; 7528 + var zoom2 = this._map.getZoom(); 7529 + if (zoom2 > this.options.maxZoom || zoom2 < this.options.minZoom) { 7530 + this._removeAllTiles(); 7531 + return; 7532 + } 7533 + for (key in this._tiles) { 7534 + tile = this._tiles[key]; 7535 + tile.retain = tile.current; 7536 + } 7537 + for (key in this._tiles) { 7538 + tile = this._tiles[key]; 7539 + if (tile.current && !tile.active) { 7540 + var coords = tile.coords; 7541 + if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) { 7542 + this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2); 7543 + } 7544 + } 7545 + } 7546 + for (key in this._tiles) { 7547 + if (!this._tiles[key].retain) { 7548 + this._removeTile(key); 7549 + } 7550 + } 7551 + }, 7552 + _removeTilesAtZoom: function(zoom2) { 7553 + for (var key in this._tiles) { 7554 + if (this._tiles[key].coords.z !== zoom2) { 7555 + continue; 7556 + } 7557 + this._removeTile(key); 7558 + } 7559 + }, 7560 + _removeAllTiles: function() { 7561 + for (var key in this._tiles) { 7562 + this._removeTile(key); 7563 + } 7564 + }, 7565 + _invalidateAll: function() { 7566 + for (var z in this._levels) { 7567 + remove(this._levels[z].el); 7568 + this._onRemoveLevel(Number(z)); 7569 + delete this._levels[z]; 7570 + } 7571 + this._removeAllTiles(); 7572 + this._tileZoom = void 0; 7573 + }, 7574 + _retainParent: function(x, y, z, minZoom) { 7575 + var x2 = Math.floor(x / 2), y2 = Math.floor(y / 2), z2 = z - 1, coords2 = new Point(+x2, +y2); 7576 + coords2.z = +z2; 7577 + var key = this._tileCoordsToKey(coords2), tile = this._tiles[key]; 7578 + if (tile && tile.active) { 7579 + tile.retain = true; 7580 + return true; 7581 + } else if (tile && tile.loaded) { 7582 + tile.retain = true; 7583 + } 7584 + if (z2 > minZoom) { 7585 + return this._retainParent(x2, y2, z2, minZoom); 7586 + } 7587 + return false; 7588 + }, 7589 + _retainChildren: function(x, y, z, maxZoom) { 7590 + for (var i = 2 * x; i < 2 * x + 2; i++) { 7591 + for (var j = 2 * y; j < 2 * y + 2; j++) { 7592 + var coords = new Point(i, j); 7593 + coords.z = z + 1; 7594 + var key = this._tileCoordsToKey(coords), tile = this._tiles[key]; 7595 + if (tile && tile.active) { 7596 + tile.retain = true; 7597 + continue; 7598 + } else if (tile && tile.loaded) { 7599 + tile.retain = true; 7600 + } 7601 + if (z + 1 < maxZoom) { 7602 + this._retainChildren(i, j, z + 1, maxZoom); 7603 + } 7604 + } 7605 + } 7606 + }, 7607 + _resetView: function(e) { 7608 + var animating = e && (e.pinch || e.flyTo); 7609 + this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating); 7610 + }, 7611 + _animateZoom: function(e) { 7612 + this._setView(e.center, e.zoom, true, e.noUpdate); 7613 + }, 7614 + _clampZoom: function(zoom2) { 7615 + var options = this.options; 7616 + if (void 0 !== options.minNativeZoom && zoom2 < options.minNativeZoom) { 7617 + return options.minNativeZoom; 7618 + } 7619 + if (void 0 !== options.maxNativeZoom && options.maxNativeZoom < zoom2) { 7620 + return options.maxNativeZoom; 7621 + } 7622 + return zoom2; 7623 + }, 7624 + _setView: function(center, zoom2, noPrune, noUpdate) { 7625 + var tileZoom = Math.round(zoom2); 7626 + if (this.options.maxZoom !== void 0 && tileZoom > this.options.maxZoom || this.options.minZoom !== void 0 && tileZoom < this.options.minZoom) { 7627 + tileZoom = void 0; 7628 + } else { 7629 + tileZoom = this._clampZoom(tileZoom); 7630 + } 7631 + var tileZoomChanged = this.options.updateWhenZooming && tileZoom !== this._tileZoom; 7632 + if (!noUpdate || tileZoomChanged) { 7633 + this._tileZoom = tileZoom; 7634 + if (this._abortLoading) { 7635 + this._abortLoading(); 7636 + } 7637 + this._updateLevels(); 7638 + this._resetGrid(); 7639 + if (tileZoom !== void 0) { 7640 + this._update(center); 7641 + } 7642 + if (!noPrune) { 7643 + this._pruneTiles(); 7644 + } 7645 + this._noPrune = !!noPrune; 7646 + } 7647 + this._setZoomTransforms(center, zoom2); 7648 + }, 7649 + _setZoomTransforms: function(center, zoom2) { 7650 + for (var i in this._levels) { 7651 + this._setZoomTransform(this._levels[i], center, zoom2); 7652 + } 7653 + }, 7654 + _setZoomTransform: function(level, center, zoom2) { 7655 + var scale2 = this._map.getZoomScale(zoom2, level.zoom), translate = level.origin.multiplyBy(scale2).subtract(this._map._getNewPixelOrigin(center, zoom2)).round(); 7656 + if (Browser.any3d) { 7657 + setTransform(level.el, translate, scale2); 7658 + } else { 7659 + setPosition(level.el, translate); 7660 + } 7661 + }, 7662 + _resetGrid: function() { 7663 + var map2 = this._map, crs = map2.options.crs, tileSize = this._tileSize = this.getTileSize(), tileZoom = this._tileZoom; 7664 + var bounds = this._map.getPixelWorldBounds(this._tileZoom); 7665 + if (bounds) { 7666 + this._globalTileRange = this._pxBoundsToTileRange(bounds); 7667 + } 7668 + this._wrapX = crs.wrapLng && !this.options.noWrap && [ 7669 + Math.floor(map2.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x), 7670 + Math.ceil(map2.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y) 7671 + ]; 7672 + this._wrapY = crs.wrapLat && !this.options.noWrap && [ 7673 + Math.floor(map2.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x), 7674 + Math.ceil(map2.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y) 7675 + ]; 7676 + }, 7677 + _onMoveEnd: function() { 7678 + if (!this._map || this._map._animatingZoom) { 7679 + return; 7680 + } 7681 + this._update(); 7682 + }, 7683 + _getTiledPixelBounds: function(center) { 7684 + var map2 = this._map, mapZoom = map2._animatingZoom ? Math.max(map2._animateToZoom, map2.getZoom()) : map2.getZoom(), scale2 = map2.getZoomScale(mapZoom, this._tileZoom), pixelCenter = map2.project(center, this._tileZoom).floor(), halfSize = map2.getSize().divideBy(scale2 * 2); 7685 + return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize)); 7686 + }, 7687 + // Private method to load tiles in the grid's active zoom level according to map bounds 7688 + _update: function(center) { 7689 + var map2 = this._map; 7690 + if (!map2) { 7691 + return; 7692 + } 7693 + var zoom2 = this._clampZoom(map2.getZoom()); 7694 + if (center === void 0) { 7695 + center = map2.getCenter(); 7696 + } 7697 + if (this._tileZoom === void 0) { 7698 + return; 7699 + } 7700 + var pixelBounds = this._getTiledPixelBounds(center), tileRange = this._pxBoundsToTileRange(pixelBounds), tileCenter = tileRange.getCenter(), queue = [], margin = this.options.keepBuffer, noPruneRange = new Bounds( 7701 + tileRange.getBottomLeft().subtract([margin, -margin]), 7702 + tileRange.getTopRight().add([margin, -margin]) 7703 + ); 7704 + if (!(isFinite(tileRange.min.x) && isFinite(tileRange.min.y) && isFinite(tileRange.max.x) && isFinite(tileRange.max.y))) { 7705 + throw new Error("Attempted to load an infinite number of tiles"); 7706 + } 7707 + for (var key in this._tiles) { 7708 + var c = this._tiles[key].coords; 7709 + if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) { 7710 + this._tiles[key].current = false; 7711 + } 7712 + } 7713 + if (Math.abs(zoom2 - this._tileZoom) > 1) { 7714 + this._setView(center, zoom2); 7715 + return; 7716 + } 7717 + for (var j = tileRange.min.y; j <= tileRange.max.y; j++) { 7718 + for (var i = tileRange.min.x; i <= tileRange.max.x; i++) { 7719 + var coords = new Point(i, j); 7720 + coords.z = this._tileZoom; 7721 + if (!this._isValidTile(coords)) { 7722 + continue; 7723 + } 7724 + var tile = this._tiles[this._tileCoordsToKey(coords)]; 7725 + if (tile) { 7726 + tile.current = true; 7727 + } else { 7728 + queue.push(coords); 7729 + } 7730 + } 7731 + } 7732 + queue.sort(function(a, b) { 7733 + return a.distanceTo(tileCenter) - b.distanceTo(tileCenter); 7734 + }); 7735 + if (queue.length !== 0) { 7736 + if (!this._loading) { 7737 + this._loading = true; 7738 + this.fire("loading"); 7739 + } 7740 + var fragment = document.createDocumentFragment(); 7741 + for (i = 0; i < queue.length; i++) { 7742 + this._addTile(queue[i], fragment); 7743 + } 7744 + this._level.el.appendChild(fragment); 7745 + } 7746 + }, 7747 + _isValidTile: function(coords) { 7748 + var crs = this._map.options.crs; 7749 + if (!crs.infinite) { 7750 + var bounds = this._globalTileRange; 7751 + if (!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x) || !crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y)) { 7752 + return false; 7753 + } 7754 + } 7755 + if (!this.options.bounds) { 7756 + return true; 7757 + } 7758 + var tileBounds = this._tileCoordsToBounds(coords); 7759 + return toLatLngBounds(this.options.bounds).overlaps(tileBounds); 7760 + }, 7761 + _keyToBounds: function(key) { 7762 + return this._tileCoordsToBounds(this._keyToTileCoords(key)); 7763 + }, 7764 + _tileCoordsToNwSe: function(coords) { 7765 + var map2 = this._map, tileSize = this.getTileSize(), nwPoint = coords.scaleBy(tileSize), sePoint = nwPoint.add(tileSize), nw = map2.unproject(nwPoint, coords.z), se = map2.unproject(sePoint, coords.z); 7766 + return [nw, se]; 7767 + }, 7768 + // converts tile coordinates to its geographical bounds 7769 + _tileCoordsToBounds: function(coords) { 7770 + var bp = this._tileCoordsToNwSe(coords), bounds = new LatLngBounds(bp[0], bp[1]); 7771 + if (!this.options.noWrap) { 7772 + bounds = this._map.wrapLatLngBounds(bounds); 7773 + } 7774 + return bounds; 7775 + }, 7776 + // converts tile coordinates to key for the tile cache 7777 + _tileCoordsToKey: function(coords) { 7778 + return coords.x + ":" + coords.y + ":" + coords.z; 7779 + }, 7780 + // converts tile cache key to coordinates 7781 + _keyToTileCoords: function(key) { 7782 + var k = key.split(":"), coords = new Point(+k[0], +k[1]); 7783 + coords.z = +k[2]; 7784 + return coords; 7785 + }, 7786 + _removeTile: function(key) { 7787 + var tile = this._tiles[key]; 7788 + if (!tile) { 7789 + return; 7790 + } 7791 + remove(tile.el); 7792 + delete this._tiles[key]; 7793 + this.fire("tileunload", { 7794 + tile: tile.el, 7795 + coords: this._keyToTileCoords(key) 7796 + }); 7797 + }, 7798 + _initTile: function(tile) { 7799 + addClass(tile, "leaflet-tile"); 7800 + var tileSize = this.getTileSize(); 7801 + tile.style.width = tileSize.x + "px"; 7802 + tile.style.height = tileSize.y + "px"; 7803 + tile.onselectstart = falseFn; 7804 + tile.onmousemove = falseFn; 7805 + if (Browser.ielt9 && this.options.opacity < 1) { 7806 + setOpacity(tile, this.options.opacity); 7807 + } 7808 + }, 7809 + _addTile: function(coords, container) { 7810 + var tilePos = this._getTilePos(coords), key = this._tileCoordsToKey(coords); 7811 + var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords)); 7812 + this._initTile(tile); 7813 + if (this.createTile.length < 2) { 7814 + requestAnimFrame(bind(this._tileReady, this, coords, null, tile)); 7815 + } 7816 + setPosition(tile, tilePos); 7817 + this._tiles[key] = { 7818 + el: tile, 7819 + coords, 7820 + current: true 7821 + }; 7822 + container.appendChild(tile); 7823 + this.fire("tileloadstart", { 7824 + tile, 7825 + coords 7826 + }); 7827 + }, 7828 + _tileReady: function(coords, err, tile) { 7829 + if (err) { 7830 + this.fire("tileerror", { 7831 + error: err, 7832 + tile, 7833 + coords 7834 + }); 7835 + } 7836 + var key = this._tileCoordsToKey(coords); 7837 + tile = this._tiles[key]; 7838 + if (!tile) { 7839 + return; 7840 + } 7841 + tile.loaded = +/* @__PURE__ */ new Date(); 7842 + if (this._map._fadeAnimated) { 7843 + setOpacity(tile.el, 0); 7844 + cancelAnimFrame(this._fadeFrame); 7845 + this._fadeFrame = requestAnimFrame(this._updateOpacity, this); 7846 + } else { 7847 + tile.active = true; 7848 + this._pruneTiles(); 7849 + } 7850 + if (!err) { 7851 + addClass(tile.el, "leaflet-tile-loaded"); 7852 + this.fire("tileload", { 7853 + tile: tile.el, 7854 + coords 7855 + }); 7856 + } 7857 + if (this._noTilesToLoad()) { 7858 + this._loading = false; 7859 + this.fire("load"); 7860 + if (Browser.ielt9 || !this._map._fadeAnimated) { 7861 + requestAnimFrame(this._pruneTiles, this); 7862 + } else { 7863 + setTimeout(bind(this._pruneTiles, this), 250); 7864 + } 7865 + } 7866 + }, 7867 + _getTilePos: function(coords) { 7868 + return coords.scaleBy(this.getTileSize()).subtract(this._level.origin); 7869 + }, 7870 + _wrapCoords: function(coords) { 7871 + var newCoords = new Point( 7872 + this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x, 7873 + this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y 7874 + ); 7875 + newCoords.z = coords.z; 7876 + return newCoords; 7877 + }, 7878 + _pxBoundsToTileRange: function(bounds) { 7879 + var tileSize = this.getTileSize(); 7880 + return new Bounds( 7881 + bounds.min.unscaleBy(tileSize).floor(), 7882 + bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]) 7883 + ); 7884 + }, 7885 + _noTilesToLoad: function() { 7886 + for (var key in this._tiles) { 7887 + if (!this._tiles[key].loaded) { 7888 + return false; 7889 + } 7890 + } 7891 + return true; 7892 + } 7893 + }); 7894 + function gridLayer(options) { 7895 + return new GridLayer(options); 7896 + } 7897 + var TileLayer = GridLayer.extend({ 7898 + // @section 7899 + // @aka TileLayer options 7900 + options: { 7901 + // @option minZoom: Number = 0 7902 + // The minimum zoom level down to which this layer will be displayed (inclusive). 7903 + minZoom: 0, 7904 + // @option maxZoom: Number = 18 7905 + // The maximum zoom level up to which this layer will be displayed (inclusive). 7906 + maxZoom: 18, 7907 + // @option subdomains: String|String[] = 'abc' 7908 + // Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings. 7909 + subdomains: "abc", 7910 + // @option errorTileUrl: String = '' 7911 + // URL to the tile image to show in place of the tile that failed to load. 7912 + errorTileUrl: "", 7913 + // @option zoomOffset: Number = 0 7914 + // The zoom number used in tile URLs will be offset with this value. 7915 + zoomOffset: 0, 7916 + // @option tms: Boolean = false 7917 + // If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services). 7918 + tms: false, 7919 + // @option zoomReverse: Boolean = false 7920 + // If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`) 7921 + zoomReverse: false, 7922 + // @option detectRetina: Boolean = false 7923 + // If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution. 7924 + detectRetina: false, 7925 + // @option crossOrigin: Boolean|String = false 7926 + // Whether the crossOrigin attribute will be added to the tiles. 7927 + // If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data. 7928 + // Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values. 7929 + crossOrigin: false, 7930 + // @option referrerPolicy: Boolean|String = false 7931 + // Whether the referrerPolicy attribute will be added to the tiles. 7932 + // If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided. 7933 + // This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer 7934 + // (e.g. to validate an API token). 7935 + // Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values. 7936 + referrerPolicy: false 7937 + }, 7938 + initialize: function(url, options) { 7939 + this._url = url; 7940 + options = setOptions(this, options); 7941 + if (options.detectRetina && Browser.retina && options.maxZoom > 0) { 7942 + options.tileSize = Math.floor(options.tileSize / 2); 7943 + if (!options.zoomReverse) { 7944 + options.zoomOffset++; 7945 + options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1); 7946 + } else { 7947 + options.zoomOffset--; 7948 + options.minZoom = Math.min(options.maxZoom, options.minZoom + 1); 7949 + } 7950 + options.minZoom = Math.max(0, options.minZoom); 7951 + } else if (!options.zoomReverse) { 7952 + options.maxZoom = Math.max(options.minZoom, options.maxZoom); 7953 + } else { 7954 + options.minZoom = Math.min(options.maxZoom, options.minZoom); 7955 + } 7956 + if (typeof options.subdomains === "string") { 7957 + options.subdomains = options.subdomains.split(""); 7958 + } 7959 + this.on("tileunload", this._onTileRemove); 7960 + }, 7961 + // @method setUrl(url: String, noRedraw?: Boolean): this 7962 + // Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`). 7963 + // If the URL does not change, the layer will not be redrawn unless 7964 + // the noRedraw parameter is set to false. 7965 + setUrl: function(url, noRedraw) { 7966 + if (this._url === url && noRedraw === void 0) { 7967 + noRedraw = true; 7968 + } 7969 + this._url = url; 7970 + if (!noRedraw) { 7971 + this.redraw(); 7972 + } 7973 + return this; 7974 + }, 7975 + // @method createTile(coords: Object, done?: Function): HTMLElement 7976 + // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile) 7977 + // to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done` 7978 + // callback is called when the tile has been loaded. 7979 + createTile: function(coords, done) { 7980 + var tile = document.createElement("img"); 7981 + on(tile, "load", bind(this._tileOnLoad, this, done, tile)); 7982 + on(tile, "error", bind(this._tileOnError, this, done, tile)); 7983 + if (this.options.crossOrigin || this.options.crossOrigin === "") { 7984 + tile.crossOrigin = this.options.crossOrigin === true ? "" : this.options.crossOrigin; 7985 + } 7986 + if (typeof this.options.referrerPolicy === "string") { 7987 + tile.referrerPolicy = this.options.referrerPolicy; 7988 + } 7989 + tile.alt = ""; 7990 + tile.src = this.getTileUrl(coords); 7991 + return tile; 7992 + }, 7993 + // @section Extension methods 7994 + // @uninheritable 7995 + // Layers extending `TileLayer` might reimplement the following method. 7996 + // @method getTileUrl(coords: Object): String 7997 + // Called only internally, returns the URL for a tile given its coordinates. 7998 + // Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes. 7999 + getTileUrl: function(coords) { 8000 + var data = { 8001 + r: Browser.retina ? "@2x" : "", 8002 + s: this._getSubdomain(coords), 8003 + x: coords.x, 8004 + y: coords.y, 8005 + z: this._getZoomForUrl() 8006 + }; 8007 + if (this._map && !this._map.options.crs.infinite) { 8008 + var invertedY = this._globalTileRange.max.y - coords.y; 8009 + if (this.options.tms) { 8010 + data["y"] = invertedY; 8011 + } 8012 + data["-y"] = invertedY; 8013 + } 8014 + return template(this._url, extend(data, this.options)); 8015 + }, 8016 + _tileOnLoad: function(done, tile) { 8017 + if (Browser.ielt9) { 8018 + setTimeout(bind(done, this, null, tile), 0); 8019 + } else { 8020 + done(null, tile); 8021 + } 8022 + }, 8023 + _tileOnError: function(done, tile, e) { 8024 + var errorUrl = this.options.errorTileUrl; 8025 + if (errorUrl && tile.getAttribute("src") !== errorUrl) { 8026 + tile.src = errorUrl; 8027 + } 8028 + done(e, tile); 8029 + }, 8030 + _onTileRemove: function(e) { 8031 + e.tile.onload = null; 8032 + }, 8033 + _getZoomForUrl: function() { 8034 + var zoom2 = this._tileZoom, maxZoom = this.options.maxZoom, zoomReverse = this.options.zoomReverse, zoomOffset = this.options.zoomOffset; 8035 + if (zoomReverse) { 8036 + zoom2 = maxZoom - zoom2; 8037 + } 8038 + return zoom2 + zoomOffset; 8039 + }, 8040 + _getSubdomain: function(tilePoint) { 8041 + var index2 = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length; 8042 + return this.options.subdomains[index2]; 8043 + }, 8044 + // stops loading all tiles in the background layer 8045 + _abortLoading: function() { 8046 + var i, tile; 8047 + for (i in this._tiles) { 8048 + if (this._tiles[i].coords.z !== this._tileZoom) { 8049 + tile = this._tiles[i].el; 8050 + tile.onload = falseFn; 8051 + tile.onerror = falseFn; 8052 + if (!tile.complete) { 8053 + tile.src = emptyImageUrl; 8054 + var coords = this._tiles[i].coords; 8055 + remove(tile); 8056 + delete this._tiles[i]; 8057 + this.fire("tileabort", { 8058 + tile, 8059 + coords 8060 + }); 8061 + } 8062 + } 8063 + } 8064 + }, 8065 + _removeTile: function(key) { 8066 + var tile = this._tiles[key]; 8067 + if (!tile) { 8068 + return; 8069 + } 8070 + tile.el.setAttribute("src", emptyImageUrl); 8071 + return GridLayer.prototype._removeTile.call(this, key); 8072 + }, 8073 + _tileReady: function(coords, err, tile) { 8074 + if (!this._map || tile && tile.getAttribute("src") === emptyImageUrl) { 8075 + return; 8076 + } 8077 + return GridLayer.prototype._tileReady.call(this, coords, err, tile); 8078 + } 8079 + }); 8080 + function tileLayer2(url, options) { 8081 + return new TileLayer(url, options); 8082 + } 8083 + var TileLayerWMS = TileLayer.extend({ 8084 + // @section 8085 + // @aka TileLayer.WMS options 8086 + // If any custom options not documented here are used, they will be sent to the 8087 + // WMS server as extra parameters in each request URL. This can be useful for 8088 + // [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html). 8089 + defaultWmsParams: { 8090 + service: "WMS", 8091 + request: "GetMap", 8092 + // @option layers: String = '' 8093 + // **(required)** Comma-separated list of WMS layers to show. 8094 + layers: "", 8095 + // @option styles: String = '' 8096 + // Comma-separated list of WMS styles. 8097 + styles: "", 8098 + // @option format: String = 'image/jpeg' 8099 + // WMS image format (use `'image/png'` for layers with transparency). 8100 + format: "image/jpeg", 8101 + // @option transparent: Boolean = false 8102 + // If `true`, the WMS service will return images with transparency. 8103 + transparent: false, 8104 + // @option version: String = '1.1.1' 8105 + // Version of the WMS service to use 8106 + version: "1.1.1" 8107 + }, 8108 + options: { 8109 + // @option crs: CRS = null 8110 + // Coordinate Reference System to use for the WMS requests, defaults to 8111 + // map CRS. Don't change this if you're not sure what it means. 8112 + crs: null, 8113 + // @option uppercase: Boolean = false 8114 + // If `true`, WMS request parameter keys will be uppercase. 8115 + uppercase: false 8116 + }, 8117 + initialize: function(url, options) { 8118 + this._url = url; 8119 + var wmsParams = extend({}, this.defaultWmsParams); 8120 + for (var i in options) { 8121 + if (!(i in this.options)) { 8122 + wmsParams[i] = options[i]; 8123 + } 8124 + } 8125 + options = setOptions(this, options); 8126 + var realRetina = options.detectRetina && Browser.retina ? 2 : 1; 8127 + var tileSize = this.getTileSize(); 8128 + wmsParams.width = tileSize.x * realRetina; 8129 + wmsParams.height = tileSize.y * realRetina; 8130 + this.wmsParams = wmsParams; 8131 + }, 8132 + onAdd: function(map2) { 8133 + this._crs = this.options.crs || map2.options.crs; 8134 + this._wmsVersion = parseFloat(this.wmsParams.version); 8135 + var projectionKey = this._wmsVersion >= 1.3 ? "crs" : "srs"; 8136 + this.wmsParams[projectionKey] = this._crs.code; 8137 + TileLayer.prototype.onAdd.call(this, map2); 8138 + }, 8139 + getTileUrl: function(coords) { 8140 + var tileBounds = this._tileCoordsToNwSe(coords), crs = this._crs, bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])), min = bounds.min, max = bounds.max, bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ? [min.y, min.x, max.y, max.x] : [min.x, min.y, max.x, max.y]).join(","), url = TileLayer.prototype.getTileUrl.call(this, coords); 8141 + return url + getParamString(this.wmsParams, url, this.options.uppercase) + (this.options.uppercase ? "&BBOX=" : "&bbox=") + bbox; 8142 + }, 8143 + // @method setParams(params: Object, noRedraw?: Boolean): this 8144 + // Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true). 8145 + setParams: function(params, noRedraw) { 8146 + extend(this.wmsParams, params); 8147 + if (!noRedraw) { 8148 + this.redraw(); 8149 + } 8150 + return this; 8151 + } 8152 + }); 8153 + function tileLayerWMS(url, options) { 8154 + return new TileLayerWMS(url, options); 8155 + } 8156 + TileLayer.WMS = TileLayerWMS; 8157 + tileLayer2.wms = tileLayerWMS; 8158 + var Renderer = Layer.extend({ 8159 + // @section 8160 + // @aka Renderer options 8161 + options: { 8162 + // @option padding: Number = 0.1 8163 + // How much to extend the clip area around the map view (relative to its size) 8164 + // e.g. 0.1 would be 10% of map view in each direction 8165 + padding: 0.1 8166 + }, 8167 + initialize: function(options) { 8168 + setOptions(this, options); 8169 + stamp(this); 8170 + this._layers = this._layers || {}; 8171 + }, 8172 + onAdd: function() { 8173 + if (!this._container) { 8174 + this._initContainer(); 8175 + addClass(this._container, "leaflet-zoom-animated"); 8176 + } 8177 + this.getPane().appendChild(this._container); 8178 + this._update(); 8179 + this.on("update", this._updatePaths, this); 8180 + }, 8181 + onRemove: function() { 8182 + this.off("update", this._updatePaths, this); 8183 + this._destroyContainer(); 8184 + }, 8185 + getEvents: function() { 8186 + var events = { 8187 + viewreset: this._reset, 8188 + zoom: this._onZoom, 8189 + moveend: this._update, 8190 + zoomend: this._onZoomEnd 8191 + }; 8192 + if (this._zoomAnimated) { 8193 + events.zoomanim = this._onAnimZoom; 8194 + } 8195 + return events; 8196 + }, 8197 + _onAnimZoom: function(ev) { 8198 + this._updateTransform(ev.center, ev.zoom); 8199 + }, 8200 + _onZoom: function() { 8201 + this._updateTransform(this._map.getCenter(), this._map.getZoom()); 8202 + }, 8203 + _updateTransform: function(center, zoom2) { 8204 + var scale2 = this._map.getZoomScale(zoom2, this._zoom), viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding), currentCenterPoint = this._map.project(this._center, zoom2), topLeftOffset = viewHalf.multiplyBy(-scale2).add(currentCenterPoint).subtract(this._map._getNewPixelOrigin(center, zoom2)); 8205 + if (Browser.any3d) { 8206 + setTransform(this._container, topLeftOffset, scale2); 8207 + } else { 8208 + setPosition(this._container, topLeftOffset); 8209 + } 8210 + }, 8211 + _reset: function() { 8212 + this._update(); 8213 + this._updateTransform(this._center, this._zoom); 8214 + for (var id in this._layers) { 8215 + this._layers[id]._reset(); 8216 + } 8217 + }, 8218 + _onZoomEnd: function() { 8219 + for (var id in this._layers) { 8220 + this._layers[id]._project(); 8221 + } 8222 + }, 8223 + _updatePaths: function() { 8224 + for (var id in this._layers) { 8225 + this._layers[id]._update(); 8226 + } 8227 + }, 8228 + _update: function() { 8229 + var p = this.options.padding, size = this._map.getSize(), min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round(); 8230 + this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round()); 8231 + this._center = this._map.getCenter(); 8232 + this._zoom = this._map.getZoom(); 8233 + } 8234 + }); 8235 + var Canvas = Renderer.extend({ 8236 + // @section 8237 + // @aka Canvas options 8238 + options: { 8239 + // @option tolerance: Number = 0 8240 + // How much to extend the click tolerance around a path/object on the map. 8241 + tolerance: 0 8242 + }, 8243 + getEvents: function() { 8244 + var events = Renderer.prototype.getEvents.call(this); 8245 + events.viewprereset = this._onViewPreReset; 8246 + return events; 8247 + }, 8248 + _onViewPreReset: function() { 8249 + this._postponeUpdatePaths = true; 8250 + }, 8251 + onAdd: function() { 8252 + Renderer.prototype.onAdd.call(this); 8253 + this._draw(); 8254 + }, 8255 + _initContainer: function() { 8256 + var container = this._container = document.createElement("canvas"); 8257 + on(container, "mousemove", this._onMouseMove, this); 8258 + on(container, "click dblclick mousedown mouseup contextmenu", this._onClick, this); 8259 + on(container, "mouseout", this._handleMouseOut, this); 8260 + container["_leaflet_disable_events"] = true; 8261 + this._ctx = container.getContext("2d"); 8262 + }, 8263 + _destroyContainer: function() { 8264 + cancelAnimFrame(this._redrawRequest); 8265 + delete this._ctx; 8266 + remove(this._container); 8267 + off(this._container); 8268 + delete this._container; 8269 + }, 8270 + _updatePaths: function() { 8271 + if (this._postponeUpdatePaths) { 8272 + return; 8273 + } 8274 + var layer; 8275 + this._redrawBounds = null; 8276 + for (var id in this._layers) { 8277 + layer = this._layers[id]; 8278 + layer._update(); 8279 + } 8280 + this._redraw(); 8281 + }, 8282 + _update: function() { 8283 + if (this._map._animatingZoom && this._bounds) { 8284 + return; 8285 + } 8286 + Renderer.prototype._update.call(this); 8287 + var b = this._bounds, container = this._container, size = b.getSize(), m = Browser.retina ? 2 : 1; 8288 + setPosition(container, b.min); 8289 + container.width = m * size.x; 8290 + container.height = m * size.y; 8291 + container.style.width = size.x + "px"; 8292 + container.style.height = size.y + "px"; 8293 + if (Browser.retina) { 8294 + this._ctx.scale(2, 2); 8295 + } 8296 + this._ctx.translate(-b.min.x, -b.min.y); 8297 + this.fire("update"); 8298 + }, 8299 + _reset: function() { 8300 + Renderer.prototype._reset.call(this); 8301 + if (this._postponeUpdatePaths) { 8302 + this._postponeUpdatePaths = false; 8303 + this._updatePaths(); 8304 + } 8305 + }, 8306 + _initPath: function(layer) { 8307 + this._updateDashArray(layer); 8308 + this._layers[stamp(layer)] = layer; 8309 + var order = layer._order = { 8310 + layer, 8311 + prev: this._drawLast, 8312 + next: null 8313 + }; 8314 + if (this._drawLast) { 8315 + this._drawLast.next = order; 8316 + } 8317 + this._drawLast = order; 8318 + this._drawFirst = this._drawFirst || this._drawLast; 8319 + }, 8320 + _addPath: function(layer) { 8321 + this._requestRedraw(layer); 8322 + }, 8323 + _removePath: function(layer) { 8324 + var order = layer._order; 8325 + var next = order.next; 8326 + var prev = order.prev; 8327 + if (next) { 8328 + next.prev = prev; 8329 + } else { 8330 + this._drawLast = prev; 8331 + } 8332 + if (prev) { 8333 + prev.next = next; 8334 + } else { 8335 + this._drawFirst = next; 8336 + } 8337 + delete layer._order; 8338 + delete this._layers[stamp(layer)]; 8339 + this._requestRedraw(layer); 8340 + }, 8341 + _updatePath: function(layer) { 8342 + this._extendRedrawBounds(layer); 8343 + layer._project(); 8344 + layer._update(); 8345 + this._requestRedraw(layer); 8346 + }, 8347 + _updateStyle: function(layer) { 8348 + this._updateDashArray(layer); 8349 + this._requestRedraw(layer); 8350 + }, 8351 + _updateDashArray: function(layer) { 8352 + if (typeof layer.options.dashArray === "string") { 8353 + var parts = layer.options.dashArray.split(/[, ]+/), dashArray = [], dashValue, i; 8354 + for (i = 0; i < parts.length; i++) { 8355 + dashValue = Number(parts[i]); 8356 + if (isNaN(dashValue)) { 8357 + return; 8358 + } 8359 + dashArray.push(dashValue); 8360 + } 8361 + layer.options._dashArray = dashArray; 8362 + } else { 8363 + layer.options._dashArray = layer.options.dashArray; 8364 + } 8365 + }, 8366 + _requestRedraw: function(layer) { 8367 + if (!this._map) { 8368 + return; 8369 + } 8370 + this._extendRedrawBounds(layer); 8371 + this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this); 8372 + }, 8373 + _extendRedrawBounds: function(layer) { 8374 + if (layer._pxBounds) { 8375 + var padding = (layer.options.weight || 0) + 1; 8376 + this._redrawBounds = this._redrawBounds || new Bounds(); 8377 + this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding])); 8378 + this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding])); 8379 + } 8380 + }, 8381 + _redraw: function() { 8382 + this._redrawRequest = null; 8383 + if (this._redrawBounds) { 8384 + this._redrawBounds.min._floor(); 8385 + this._redrawBounds.max._ceil(); 8386 + } 8387 + this._clear(); 8388 + this._draw(); 8389 + this._redrawBounds = null; 8390 + }, 8391 + _clear: function() { 8392 + var bounds = this._redrawBounds; 8393 + if (bounds) { 8394 + var size = bounds.getSize(); 8395 + this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y); 8396 + } else { 8397 + this._ctx.save(); 8398 + this._ctx.setTransform(1, 0, 0, 1, 0, 0); 8399 + this._ctx.clearRect(0, 0, this._container.width, this._container.height); 8400 + this._ctx.restore(); 8401 + } 8402 + }, 8403 + _draw: function() { 8404 + var layer, bounds = this._redrawBounds; 8405 + this._ctx.save(); 8406 + if (bounds) { 8407 + var size = bounds.getSize(); 8408 + this._ctx.beginPath(); 8409 + this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y); 8410 + this._ctx.clip(); 8411 + } 8412 + this._drawing = true; 8413 + for (var order = this._drawFirst; order; order = order.next) { 8414 + layer = order.layer; 8415 + if (!bounds || layer._pxBounds && layer._pxBounds.intersects(bounds)) { 8416 + layer._updatePath(); 8417 + } 8418 + } 8419 + this._drawing = false; 8420 + this._ctx.restore(); 8421 + }, 8422 + _updatePoly: function(layer, closed) { 8423 + if (!this._drawing) { 8424 + return; 8425 + } 8426 + var i, j, len2, p, parts = layer._parts, len = parts.length, ctx = this._ctx; 8427 + if (!len) { 8428 + return; 8429 + } 8430 + ctx.beginPath(); 8431 + for (i = 0; i < len; i++) { 8432 + for (j = 0, len2 = parts[i].length; j < len2; j++) { 8433 + p = parts[i][j]; 8434 + ctx[j ? "lineTo" : "moveTo"](p.x, p.y); 8435 + } 8436 + if (closed) { 8437 + ctx.closePath(); 8438 + } 8439 + } 8440 + this._fillStroke(ctx, layer); 8441 + }, 8442 + _updateCircle: function(layer) { 8443 + if (!this._drawing || layer._empty()) { 8444 + return; 8445 + } 8446 + var p = layer._point, ctx = this._ctx, r = Math.max(Math.round(layer._radius), 1), s = (Math.max(Math.round(layer._radiusY), 1) || r) / r; 8447 + if (s !== 1) { 8448 + ctx.save(); 8449 + ctx.scale(1, s); 8450 + } 8451 + ctx.beginPath(); 8452 + ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false); 8453 + if (s !== 1) { 8454 + ctx.restore(); 8455 + } 8456 + this._fillStroke(ctx, layer); 8457 + }, 8458 + _fillStroke: function(ctx, layer) { 8459 + var options = layer.options; 8460 + if (options.fill) { 8461 + ctx.globalAlpha = options.fillOpacity; 8462 + ctx.fillStyle = options.fillColor || options.color; 8463 + ctx.fill(options.fillRule || "evenodd"); 8464 + } 8465 + if (options.stroke && options.weight !== 0) { 8466 + if (ctx.setLineDash) { 8467 + ctx.setLineDash(layer.options && layer.options._dashArray || []); 8468 + } 8469 + ctx.globalAlpha = options.opacity; 8470 + ctx.lineWidth = options.weight; 8471 + ctx.strokeStyle = options.color; 8472 + ctx.lineCap = options.lineCap; 8473 + ctx.lineJoin = options.lineJoin; 8474 + ctx.stroke(); 8475 + } 8476 + }, 8477 + // Canvas obviously doesn't have mouse events for individual drawn objects, 8478 + // so we emulate that by calculating what's under the mouse on mousemove/click manually 8479 + _onClick: function(e) { 8480 + var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer; 8481 + for (var order = this._drawFirst; order; order = order.next) { 8482 + layer = order.layer; 8483 + if (layer.options.interactive && layer._containsPoint(point)) { 8484 + if (!(e.type === "click" || e.type === "preclick") || !this._map._draggableMoved(layer)) { 8485 + clickedLayer = layer; 8486 + } 8487 + } 8488 + } 8489 + this._fireEvent(clickedLayer ? [clickedLayer] : false, e); 8490 + }, 8491 + _onMouseMove: function(e) { 8492 + if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { 8493 + return; 8494 + } 8495 + var point = this._map.mouseEventToLayerPoint(e); 8496 + this._handleMouseHover(e, point); 8497 + }, 8498 + _handleMouseOut: function(e) { 8499 + var layer = this._hoveredLayer; 8500 + if (layer) { 8501 + removeClass(this._container, "leaflet-interactive"); 8502 + this._fireEvent([layer], e, "mouseout"); 8503 + this._hoveredLayer = null; 8504 + this._mouseHoverThrottled = false; 8505 + } 8506 + }, 8507 + _handleMouseHover: function(e, point) { 8508 + if (this._mouseHoverThrottled) { 8509 + return; 8510 + } 8511 + var layer, candidateHoveredLayer; 8512 + for (var order = this._drawFirst; order; order = order.next) { 8513 + layer = order.layer; 8514 + if (layer.options.interactive && layer._containsPoint(point)) { 8515 + candidateHoveredLayer = layer; 8516 + } 8517 + } 8518 + if (candidateHoveredLayer !== this._hoveredLayer) { 8519 + this._handleMouseOut(e); 8520 + if (candidateHoveredLayer) { 8521 + addClass(this._container, "leaflet-interactive"); 8522 + this._fireEvent([candidateHoveredLayer], e, "mouseover"); 8523 + this._hoveredLayer = candidateHoveredLayer; 8524 + } 8525 + } 8526 + this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e); 8527 + this._mouseHoverThrottled = true; 8528 + setTimeout(bind(function() { 8529 + this._mouseHoverThrottled = false; 8530 + }, this), 32); 8531 + }, 8532 + _fireEvent: function(layers2, e, type) { 8533 + this._map._fireDOMEvent(e, type || e.type, layers2); 8534 + }, 8535 + _bringToFront: function(layer) { 8536 + var order = layer._order; 8537 + if (!order) { 8538 + return; 8539 + } 8540 + var next = order.next; 8541 + var prev = order.prev; 8542 + if (next) { 8543 + next.prev = prev; 8544 + } else { 8545 + return; 8546 + } 8547 + if (prev) { 8548 + prev.next = next; 8549 + } else if (next) { 8550 + this._drawFirst = next; 8551 + } 8552 + order.prev = this._drawLast; 8553 + this._drawLast.next = order; 8554 + order.next = null; 8555 + this._drawLast = order; 8556 + this._requestRedraw(layer); 8557 + }, 8558 + _bringToBack: function(layer) { 8559 + var order = layer._order; 8560 + if (!order) { 8561 + return; 8562 + } 8563 + var next = order.next; 8564 + var prev = order.prev; 8565 + if (prev) { 8566 + prev.next = next; 8567 + } else { 8568 + return; 8569 + } 8570 + if (next) { 8571 + next.prev = prev; 8572 + } else if (prev) { 8573 + this._drawLast = prev; 8574 + } 8575 + order.prev = null; 8576 + order.next = this._drawFirst; 8577 + this._drawFirst.prev = order; 8578 + this._drawFirst = order; 8579 + this._requestRedraw(layer); 8580 + } 8581 + }); 8582 + function canvas(options) { 8583 + return Browser.canvas ? new Canvas(options) : null; 8584 + } 8585 + var vmlCreate = function() { 8586 + try { 8587 + document.namespaces.add("lvml", "urn:schemas-microsoft-com:vml"); 8588 + return function(name) { 8589 + return document.createElement("<lvml:" + name + ' class="lvml">'); 8590 + }; 8591 + } catch (e) { 8592 + } 8593 + return function(name) { 8594 + return document.createElement("<" + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">'); 8595 + }; 8596 + }(); 8597 + var vmlMixin = { 8598 + _initContainer: function() { 8599 + this._container = create$1("div", "leaflet-vml-container"); 8600 + }, 8601 + _update: function() { 8602 + if (this._map._animatingZoom) { 8603 + return; 8604 + } 8605 + Renderer.prototype._update.call(this); 8606 + this.fire("update"); 8607 + }, 8608 + _initPath: function(layer) { 8609 + var container = layer._container = vmlCreate("shape"); 8610 + addClass(container, "leaflet-vml-shape " + (this.options.className || "")); 8611 + container.coordsize = "1 1"; 8612 + layer._path = vmlCreate("path"); 8613 + container.appendChild(layer._path); 8614 + this._updateStyle(layer); 8615 + this._layers[stamp(layer)] = layer; 8616 + }, 8617 + _addPath: function(layer) { 8618 + var container = layer._container; 8619 + this._container.appendChild(container); 8620 + if (layer.options.interactive) { 8621 + layer.addInteractiveTarget(container); 8622 + } 8623 + }, 8624 + _removePath: function(layer) { 8625 + var container = layer._container; 8626 + remove(container); 8627 + layer.removeInteractiveTarget(container); 8628 + delete this._layers[stamp(layer)]; 8629 + }, 8630 + _updateStyle: function(layer) { 8631 + var stroke = layer._stroke, fill = layer._fill, options = layer.options, container = layer._container; 8632 + container.stroked = !!options.stroke; 8633 + container.filled = !!options.fill; 8634 + if (options.stroke) { 8635 + if (!stroke) { 8636 + stroke = layer._stroke = vmlCreate("stroke"); 8637 + } 8638 + container.appendChild(stroke); 8639 + stroke.weight = options.weight + "px"; 8640 + stroke.color = options.color; 8641 + stroke.opacity = options.opacity; 8642 + if (options.dashArray) { 8643 + stroke.dashStyle = isArray(options.dashArray) ? options.dashArray.join(" ") : options.dashArray.replace(/( *, *)/g, " "); 8644 + } else { 8645 + stroke.dashStyle = ""; 8646 + } 8647 + stroke.endcap = options.lineCap.replace("butt", "flat"); 8648 + stroke.joinstyle = options.lineJoin; 8649 + } else if (stroke) { 8650 + container.removeChild(stroke); 8651 + layer._stroke = null; 8652 + } 8653 + if (options.fill) { 8654 + if (!fill) { 8655 + fill = layer._fill = vmlCreate("fill"); 8656 + } 8657 + container.appendChild(fill); 8658 + fill.color = options.fillColor || options.color; 8659 + fill.opacity = options.fillOpacity; 8660 + } else if (fill) { 8661 + container.removeChild(fill); 8662 + layer._fill = null; 8663 + } 8664 + }, 8665 + _updateCircle: function(layer) { 8666 + var p = layer._point.round(), r = Math.round(layer._radius), r2 = Math.round(layer._radiusY || r); 8667 + this._setPath(layer, layer._empty() ? "M0 0" : "AL " + p.x + "," + p.y + " " + r + "," + r2 + " 0," + 65535 * 360); 8668 + }, 8669 + _setPath: function(layer, path) { 8670 + layer._path.v = path; 8671 + }, 8672 + _bringToFront: function(layer) { 8673 + toFront(layer._container); 8674 + }, 8675 + _bringToBack: function(layer) { 8676 + toBack(layer._container); 8677 + } 8678 + }; 8679 + var create = Browser.vml ? vmlCreate : svgCreate; 8680 + var SVG = Renderer.extend({ 8681 + _initContainer: function() { 8682 + this._container = create("svg"); 8683 + this._container.setAttribute("pointer-events", "none"); 8684 + this._rootGroup = create("g"); 8685 + this._container.appendChild(this._rootGroup); 8686 + }, 8687 + _destroyContainer: function() { 8688 + remove(this._container); 8689 + off(this._container); 8690 + delete this._container; 8691 + delete this._rootGroup; 8692 + delete this._svgSize; 8693 + }, 8694 + _update: function() { 8695 + if (this._map._animatingZoom && this._bounds) { 8696 + return; 8697 + } 8698 + Renderer.prototype._update.call(this); 8699 + var b = this._bounds, size = b.getSize(), container = this._container; 8700 + if (!this._svgSize || !this._svgSize.equals(size)) { 8701 + this._svgSize = size; 8702 + container.setAttribute("width", size.x); 8703 + container.setAttribute("height", size.y); 8704 + } 8705 + setPosition(container, b.min); 8706 + container.setAttribute("viewBox", [b.min.x, b.min.y, size.x, size.y].join(" ")); 8707 + this.fire("update"); 8708 + }, 8709 + // methods below are called by vector layers implementations 8710 + _initPath: function(layer) { 8711 + var path = layer._path = create("path"); 8712 + if (layer.options.className) { 8713 + addClass(path, layer.options.className); 8714 + } 8715 + if (layer.options.interactive) { 8716 + addClass(path, "leaflet-interactive"); 8717 + } 8718 + this._updateStyle(layer); 8719 + this._layers[stamp(layer)] = layer; 8720 + }, 8721 + _addPath: function(layer) { 8722 + if (!this._rootGroup) { 8723 + this._initContainer(); 8724 + } 8725 + this._rootGroup.appendChild(layer._path); 8726 + layer.addInteractiveTarget(layer._path); 8727 + }, 8728 + _removePath: function(layer) { 8729 + remove(layer._path); 8730 + layer.removeInteractiveTarget(layer._path); 8731 + delete this._layers[stamp(layer)]; 8732 + }, 8733 + _updatePath: function(layer) { 8734 + layer._project(); 8735 + layer._update(); 8736 + }, 8737 + _updateStyle: function(layer) { 8738 + var path = layer._path, options = layer.options; 8739 + if (!path) { 8740 + return; 8741 + } 8742 + if (options.stroke) { 8743 + path.setAttribute("stroke", options.color); 8744 + path.setAttribute("stroke-opacity", options.opacity); 8745 + path.setAttribute("stroke-width", options.weight); 8746 + path.setAttribute("stroke-linecap", options.lineCap); 8747 + path.setAttribute("stroke-linejoin", options.lineJoin); 8748 + if (options.dashArray) { 8749 + path.setAttribute("stroke-dasharray", options.dashArray); 8750 + } else { 8751 + path.removeAttribute("stroke-dasharray"); 8752 + } 8753 + if (options.dashOffset) { 8754 + path.setAttribute("stroke-dashoffset", options.dashOffset); 8755 + } else { 8756 + path.removeAttribute("stroke-dashoffset"); 8757 + } 8758 + } else { 8759 + path.setAttribute("stroke", "none"); 8760 + } 8761 + if (options.fill) { 8762 + path.setAttribute("fill", options.fillColor || options.color); 8763 + path.setAttribute("fill-opacity", options.fillOpacity); 8764 + path.setAttribute("fill-rule", options.fillRule || "evenodd"); 8765 + } else { 8766 + path.setAttribute("fill", "none"); 8767 + } 8768 + }, 8769 + _updatePoly: function(layer, closed) { 8770 + this._setPath(layer, pointsToPath(layer._parts, closed)); 8771 + }, 8772 + _updateCircle: function(layer) { 8773 + var p = layer._point, r = Math.max(Math.round(layer._radius), 1), r2 = Math.max(Math.round(layer._radiusY), 1) || r, arc = "a" + r + "," + r2 + " 0 1,0 "; 8774 + var d = layer._empty() ? "M0 0" : "M" + (p.x - r) + "," + p.y + arc + r * 2 + ",0 " + arc + -r * 2 + ",0 "; 8775 + this._setPath(layer, d); 8776 + }, 8777 + _setPath: function(layer, path) { 8778 + layer._path.setAttribute("d", path); 8779 + }, 8780 + // SVG does not have the concept of zIndex so we resort to changing the DOM order of elements 8781 + _bringToFront: function(layer) { 8782 + toFront(layer._path); 8783 + }, 8784 + _bringToBack: function(layer) { 8785 + toBack(layer._path); 8786 + } 8787 + }); 8788 + if (Browser.vml) { 8789 + SVG.include(vmlMixin); 8790 + } 8791 + function svg(options) { 8792 + return Browser.svg || Browser.vml ? new SVG(options) : null; 8793 + } 8794 + Map2.include({ 8795 + // @namespace Map; @method getRenderer(layer: Path): Renderer 8796 + // Returns the instance of `Renderer` that should be used to render the given 8797 + // `Path`. It will ensure that the `renderer` options of the map and paths 8798 + // are respected, and that the renderers do exist on the map. 8799 + getRenderer: function(layer) { 8800 + var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer; 8801 + if (!renderer) { 8802 + renderer = this._renderer = this._createRenderer(); 8803 + } 8804 + if (!this.hasLayer(renderer)) { 8805 + this.addLayer(renderer); 8806 + } 8807 + return renderer; 8808 + }, 8809 + _getPaneRenderer: function(name) { 8810 + if (name === "overlayPane" || name === void 0) { 8811 + return false; 8812 + } 8813 + var renderer = this._paneRenderers[name]; 8814 + if (renderer === void 0) { 8815 + renderer = this._createRenderer({ pane: name }); 8816 + this._paneRenderers[name] = renderer; 8817 + } 8818 + return renderer; 8819 + }, 8820 + _createRenderer: function(options) { 8821 + return this.options.preferCanvas && canvas(options) || svg(options); 8822 + } 8823 + }); 8824 + var Rectangle = Polygon.extend({ 8825 + initialize: function(latLngBounds, options) { 8826 + Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options); 8827 + }, 8828 + // @method setBounds(latLngBounds: LatLngBounds): this 8829 + // Redraws the rectangle with the passed bounds. 8830 + setBounds: function(latLngBounds) { 8831 + return this.setLatLngs(this._boundsToLatLngs(latLngBounds)); 8832 + }, 8833 + _boundsToLatLngs: function(latLngBounds) { 8834 + latLngBounds = toLatLngBounds(latLngBounds); 8835 + return [ 8836 + latLngBounds.getSouthWest(), 8837 + latLngBounds.getNorthWest(), 8838 + latLngBounds.getNorthEast(), 8839 + latLngBounds.getSouthEast() 8840 + ]; 8841 + } 8842 + }); 8843 + function rectangle(latLngBounds, options) { 8844 + return new Rectangle(latLngBounds, options); 8845 + } 8846 + SVG.create = create; 8847 + SVG.pointsToPath = pointsToPath; 8848 + GeoJSON.geometryToLayer = geometryToLayer; 8849 + GeoJSON.coordsToLatLng = coordsToLatLng; 8850 + GeoJSON.coordsToLatLngs = coordsToLatLngs; 8851 + GeoJSON.latLngToCoords = latLngToCoords; 8852 + GeoJSON.latLngsToCoords = latLngsToCoords; 8853 + GeoJSON.getFeature = getFeature; 8854 + GeoJSON.asFeature = asFeature; 8855 + Map2.mergeOptions({ 8856 + // @option boxZoom: Boolean = true 8857 + // Whether the map can be zoomed to a rectangular area specified by 8858 + // dragging the mouse while pressing the shift key. 8859 + boxZoom: true 8860 + }); 8861 + var BoxZoom = Handler.extend({ 8862 + initialize: function(map2) { 8863 + this._map = map2; 8864 + this._container = map2._container; 8865 + this._pane = map2._panes.overlayPane; 8866 + this._resetStateTimeout = 0; 8867 + map2.on("unload", this._destroy, this); 8868 + }, 8869 + addHooks: function() { 8870 + on(this._container, "mousedown", this._onMouseDown, this); 8871 + }, 8872 + removeHooks: function() { 8873 + off(this._container, "mousedown", this._onMouseDown, this); 8874 + }, 8875 + moved: function() { 8876 + return this._moved; 8877 + }, 8878 + _destroy: function() { 8879 + remove(this._pane); 8880 + delete this._pane; 8881 + }, 8882 + _resetState: function() { 8883 + this._resetStateTimeout = 0; 8884 + this._moved = false; 8885 + }, 8886 + _clearDeferredResetState: function() { 8887 + if (this._resetStateTimeout !== 0) { 8888 + clearTimeout(this._resetStateTimeout); 8889 + this._resetStateTimeout = 0; 8890 + } 8891 + }, 8892 + _onMouseDown: function(e) { 8893 + if (!e.shiftKey || e.which !== 1 && e.button !== 1) { 8894 + return false; 8895 + } 8896 + this._clearDeferredResetState(); 8897 + this._resetState(); 8898 + disableTextSelection(); 8899 + disableImageDrag(); 8900 + this._startPoint = this._map.mouseEventToContainerPoint(e); 8901 + on(document, { 8902 + contextmenu: stop, 8903 + mousemove: this._onMouseMove, 8904 + mouseup: this._onMouseUp, 8905 + keydown: this._onKeyDown 8906 + }, this); 8907 + }, 8908 + _onMouseMove: function(e) { 8909 + if (!this._moved) { 8910 + this._moved = true; 8911 + this._box = create$1("div", "leaflet-zoom-box", this._container); 8912 + addClass(this._container, "leaflet-crosshair"); 8913 + this._map.fire("boxzoomstart"); 8914 + } 8915 + this._point = this._map.mouseEventToContainerPoint(e); 8916 + var bounds = new Bounds(this._point, this._startPoint), size = bounds.getSize(); 8917 + setPosition(this._box, bounds.min); 8918 + this._box.style.width = size.x + "px"; 8919 + this._box.style.height = size.y + "px"; 8920 + }, 8921 + _finish: function() { 8922 + if (this._moved) { 8923 + remove(this._box); 8924 + removeClass(this._container, "leaflet-crosshair"); 8925 + } 8926 + enableTextSelection(); 8927 + enableImageDrag(); 8928 + off(document, { 8929 + contextmenu: stop, 8930 + mousemove: this._onMouseMove, 8931 + mouseup: this._onMouseUp, 8932 + keydown: this._onKeyDown 8933 + }, this); 8934 + }, 8935 + _onMouseUp: function(e) { 8936 + if (e.which !== 1 && e.button !== 1) { 8937 + return; 8938 + } 8939 + this._finish(); 8940 + if (!this._moved) { 8941 + return; 8942 + } 8943 + this._clearDeferredResetState(); 8944 + this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0); 8945 + var bounds = new LatLngBounds( 8946 + this._map.containerPointToLatLng(this._startPoint), 8947 + this._map.containerPointToLatLng(this._point) 8948 + ); 8949 + this._map.fitBounds(bounds).fire("boxzoomend", { boxZoomBounds: bounds }); 8950 + }, 8951 + _onKeyDown: function(e) { 8952 + if (e.keyCode === 27) { 8953 + this._finish(); 8954 + this._clearDeferredResetState(); 8955 + this._resetState(); 8956 + } 8957 + } 8958 + }); 8959 + Map2.addInitHook("addHandler", "boxZoom", BoxZoom); 8960 + Map2.mergeOptions({ 8961 + // @option doubleClickZoom: Boolean|String = true 8962 + // Whether the map can be zoomed in by double clicking on it and 8963 + // zoomed out by double clicking while holding shift. If passed 8964 + // `'center'`, double-click zoom will zoom to the center of the 8965 + // view regardless of where the mouse was. 8966 + doubleClickZoom: true 8967 + }); 8968 + var DoubleClickZoom = Handler.extend({ 8969 + addHooks: function() { 8970 + this._map.on("dblclick", this._onDoubleClick, this); 8971 + }, 8972 + removeHooks: function() { 8973 + this._map.off("dblclick", this._onDoubleClick, this); 8974 + }, 8975 + _onDoubleClick: function(e) { 8976 + var map2 = this._map, oldZoom = map2.getZoom(), delta = map2.options.zoomDelta, zoom2 = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta; 8977 + if (map2.options.doubleClickZoom === "center") { 8978 + map2.setZoom(zoom2); 8979 + } else { 8980 + map2.setZoomAround(e.containerPoint, zoom2); 8981 + } 8982 + } 8983 + }); 8984 + Map2.addInitHook("addHandler", "doubleClickZoom", DoubleClickZoom); 8985 + Map2.mergeOptions({ 8986 + // @option dragging: Boolean = true 8987 + // Whether the map is draggable with mouse/touch or not. 8988 + dragging: true, 8989 + // @section Panning Inertia Options 8990 + // @option inertia: Boolean = * 8991 + // If enabled, panning of the map will have an inertia effect where 8992 + // the map builds momentum while dragging and continues moving in 8993 + // the same direction for some time. Feels especially nice on touch 8994 + // devices. Enabled by default. 8995 + inertia: true, 8996 + // @option inertiaDeceleration: Number = 3000 8997 + // The rate with which the inertial movement slows down, in pixels/second². 8998 + inertiaDeceleration: 3400, 8999 + // px/s^2 9000 + // @option inertiaMaxSpeed: Number = Infinity 9001 + // Max speed of the inertial movement, in pixels/second. 9002 + inertiaMaxSpeed: Infinity, 9003 + // px/s 9004 + // @option easeLinearity: Number = 0.2 9005 + easeLinearity: 0.2, 9006 + // TODO refactor, move to CRS 9007 + // @option worldCopyJump: Boolean = false 9008 + // With this option enabled, the map tracks when you pan to another "copy" 9009 + // of the world and seamlessly jumps to the original one so that all overlays 9010 + // like markers and vector layers are still visible. 9011 + worldCopyJump: false, 9012 + // @option maxBoundsViscosity: Number = 0.0 9013 + // If `maxBounds` is set, this option will control how solid the bounds 9014 + // are when dragging the map around. The default value of `0.0` allows the 9015 + // user to drag outside the bounds at normal speed, higher values will 9016 + // slow down map dragging outside bounds, and `1.0` makes the bounds fully 9017 + // solid, preventing the user from dragging outside the bounds. 9018 + maxBoundsViscosity: 0 9019 + }); 9020 + var Drag = Handler.extend({ 9021 + addHooks: function() { 9022 + if (!this._draggable) { 9023 + var map2 = this._map; 9024 + this._draggable = new Draggable(map2._mapPane, map2._container); 9025 + this._draggable.on({ 9026 + dragstart: this._onDragStart, 9027 + drag: this._onDrag, 9028 + dragend: this._onDragEnd 9029 + }, this); 9030 + this._draggable.on("predrag", this._onPreDragLimit, this); 9031 + if (map2.options.worldCopyJump) { 9032 + this._draggable.on("predrag", this._onPreDragWrap, this); 9033 + map2.on("zoomend", this._onZoomEnd, this); 9034 + map2.whenReady(this._onZoomEnd, this); 9035 + } 9036 + } 9037 + addClass(this._map._container, "leaflet-grab leaflet-touch-drag"); 9038 + this._draggable.enable(); 9039 + this._positions = []; 9040 + this._times = []; 9041 + }, 9042 + removeHooks: function() { 9043 + removeClass(this._map._container, "leaflet-grab"); 9044 + removeClass(this._map._container, "leaflet-touch-drag"); 9045 + this._draggable.disable(); 9046 + }, 9047 + moved: function() { 9048 + return this._draggable && this._draggable._moved; 9049 + }, 9050 + moving: function() { 9051 + return this._draggable && this._draggable._moving; 9052 + }, 9053 + _onDragStart: function() { 9054 + var map2 = this._map; 9055 + map2._stop(); 9056 + if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) { 9057 + var bounds = toLatLngBounds(this._map.options.maxBounds); 9058 + this._offsetLimit = toBounds( 9059 + this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1), 9060 + this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1).add(this._map.getSize()) 9061 + ); 9062 + this._viscosity = Math.min(1, Math.max(0, this._map.options.maxBoundsViscosity)); 9063 + } else { 9064 + this._offsetLimit = null; 9065 + } 9066 + map2.fire("movestart").fire("dragstart"); 9067 + if (map2.options.inertia) { 9068 + this._positions = []; 9069 + this._times = []; 9070 + } 9071 + }, 9072 + _onDrag: function(e) { 9073 + if (this._map.options.inertia) { 9074 + var time = this._lastTime = +/* @__PURE__ */ new Date(), pos = this._lastPos = this._draggable._absPos || this._draggable._newPos; 9075 + this._positions.push(pos); 9076 + this._times.push(time); 9077 + this._prunePositions(time); 9078 + } 9079 + this._map.fire("move", e).fire("drag", e); 9080 + }, 9081 + _prunePositions: function(time) { 9082 + while (this._positions.length > 1 && time - this._times[0] > 50) { 9083 + this._positions.shift(); 9084 + this._times.shift(); 9085 + } 9086 + }, 9087 + _onZoomEnd: function() { 9088 + var pxCenter = this._map.getSize().divideBy(2), pxWorldCenter = this._map.latLngToLayerPoint([0, 0]); 9089 + this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x; 9090 + this._worldWidth = this._map.getPixelWorldBounds().getSize().x; 9091 + }, 9092 + _viscousLimit: function(value, threshold) { 9093 + return value - (value - threshold) * this._viscosity; 9094 + }, 9095 + _onPreDragLimit: function() { 9096 + if (!this._viscosity || !this._offsetLimit) { 9097 + return; 9098 + } 9099 + var offset = this._draggable._newPos.subtract(this._draggable._startPos); 9100 + var limit = this._offsetLimit; 9101 + if (offset.x < limit.min.x) { 9102 + offset.x = this._viscousLimit(offset.x, limit.min.x); 9103 + } 9104 + if (offset.y < limit.min.y) { 9105 + offset.y = this._viscousLimit(offset.y, limit.min.y); 9106 + } 9107 + if (offset.x > limit.max.x) { 9108 + offset.x = this._viscousLimit(offset.x, limit.max.x); 9109 + } 9110 + if (offset.y > limit.max.y) { 9111 + offset.y = this._viscousLimit(offset.y, limit.max.y); 9112 + } 9113 + this._draggable._newPos = this._draggable._startPos.add(offset); 9114 + }, 9115 + _onPreDragWrap: function() { 9116 + var worldWidth = this._worldWidth, halfWidth = Math.round(worldWidth / 2), dx = this._initialWorldOffset, x = this._draggable._newPos.x, newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx, newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx, newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2; 9117 + this._draggable._absPos = this._draggable._newPos.clone(); 9118 + this._draggable._newPos.x = newX; 9119 + }, 9120 + _onDragEnd: function(e) { 9121 + var map2 = this._map, options = map2.options, noInertia = !options.inertia || e.noInertia || this._times.length < 2; 9122 + map2.fire("dragend", e); 9123 + if (noInertia) { 9124 + map2.fire("moveend"); 9125 + } else { 9126 + this._prunePositions(+/* @__PURE__ */ new Date()); 9127 + var direction = this._lastPos.subtract(this._positions[0]), duration = (this._lastTime - this._times[0]) / 1e3, ease = options.easeLinearity, speedVector = direction.multiplyBy(ease / duration), speed = speedVector.distanceTo([0, 0]), limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); 9128 + if (!offset.x && !offset.y) { 9129 + map2.fire("moveend"); 9130 + } else { 9131 + offset = map2._limitOffset(offset, map2.options.maxBounds); 9132 + requestAnimFrame(function() { 9133 + map2.panBy(offset, { 9134 + duration: decelerationDuration, 9135 + easeLinearity: ease, 9136 + noMoveStart: true, 9137 + animate: true 9138 + }); 9139 + }); 9140 + } 9141 + } 9142 + } 9143 + }); 9144 + Map2.addInitHook("addHandler", "dragging", Drag); 9145 + Map2.mergeOptions({ 9146 + // @option keyboard: Boolean = true 9147 + // Makes the map focusable and allows users to navigate the map with keyboard 9148 + // arrows and `+`/`-` keys. 9149 + keyboard: true, 9150 + // @option keyboardPanDelta: Number = 80 9151 + // Amount of pixels to pan when pressing an arrow key. 9152 + keyboardPanDelta: 80 9153 + }); 9154 + var Keyboard = Handler.extend({ 9155 + keyCodes: { 9156 + left: [37], 9157 + right: [39], 9158 + down: [40], 9159 + up: [38], 9160 + zoomIn: [187, 107, 61, 171], 9161 + zoomOut: [189, 109, 54, 173] 9162 + }, 9163 + initialize: function(map2) { 9164 + this._map = map2; 9165 + this._setPanDelta(map2.options.keyboardPanDelta); 9166 + this._setZoomDelta(map2.options.zoomDelta); 9167 + }, 9168 + addHooks: function() { 9169 + var container = this._map._container; 9170 + if (container.tabIndex <= 0) { 9171 + container.tabIndex = "0"; 9172 + } 9173 + on(container, { 9174 + focus: this._onFocus, 9175 + blur: this._onBlur, 9176 + mousedown: this._onMouseDown 9177 + }, this); 9178 + this._map.on({ 9179 + focus: this._addHooks, 9180 + blur: this._removeHooks 9181 + }, this); 9182 + }, 9183 + removeHooks: function() { 9184 + this._removeHooks(); 9185 + off(this._map._container, { 9186 + focus: this._onFocus, 9187 + blur: this._onBlur, 9188 + mousedown: this._onMouseDown 9189 + }, this); 9190 + this._map.off({ 9191 + focus: this._addHooks, 9192 + blur: this._removeHooks 9193 + }, this); 9194 + }, 9195 + _onMouseDown: function() { 9196 + if (this._focused) { 9197 + return; 9198 + } 9199 + var body = document.body, docEl = document.documentElement, top = body.scrollTop || docEl.scrollTop, left = body.scrollLeft || docEl.scrollLeft; 9200 + this._map._container.focus(); 9201 + window.scrollTo(left, top); 9202 + }, 9203 + _onFocus: function() { 9204 + this._focused = true; 9205 + this._map.fire("focus"); 9206 + }, 9207 + _onBlur: function() { 9208 + this._focused = false; 9209 + this._map.fire("blur"); 9210 + }, 9211 + _setPanDelta: function(panDelta) { 9212 + var keys = this._panKeys = {}, codes = this.keyCodes, i, len; 9213 + for (i = 0, len = codes.left.length; i < len; i++) { 9214 + keys[codes.left[i]] = [-1 * panDelta, 0]; 9215 + } 9216 + for (i = 0, len = codes.right.length; i < len; i++) { 9217 + keys[codes.right[i]] = [panDelta, 0]; 9218 + } 9219 + for (i = 0, len = codes.down.length; i < len; i++) { 9220 + keys[codes.down[i]] = [0, panDelta]; 9221 + } 9222 + for (i = 0, len = codes.up.length; i < len; i++) { 9223 + keys[codes.up[i]] = [0, -1 * panDelta]; 9224 + } 9225 + }, 9226 + _setZoomDelta: function(zoomDelta) { 9227 + var keys = this._zoomKeys = {}, codes = this.keyCodes, i, len; 9228 + for (i = 0, len = codes.zoomIn.length; i < len; i++) { 9229 + keys[codes.zoomIn[i]] = zoomDelta; 9230 + } 9231 + for (i = 0, len = codes.zoomOut.length; i < len; i++) { 9232 + keys[codes.zoomOut[i]] = -zoomDelta; 9233 + } 9234 + }, 9235 + _addHooks: function() { 9236 + on(document, "keydown", this._onKeyDown, this); 9237 + }, 9238 + _removeHooks: function() { 9239 + off(document, "keydown", this._onKeyDown, this); 9240 + }, 9241 + _onKeyDown: function(e) { 9242 + if (e.altKey || e.ctrlKey || e.metaKey) { 9243 + return; 9244 + } 9245 + var key = e.keyCode, map2 = this._map, offset; 9246 + if (key in this._panKeys) { 9247 + if (!map2._panAnim || !map2._panAnim._inProgress) { 9248 + offset = this._panKeys[key]; 9249 + if (e.shiftKey) { 9250 + offset = toPoint(offset).multiplyBy(3); 9251 + } 9252 + if (map2.options.maxBounds) { 9253 + offset = map2._limitOffset(toPoint(offset), map2.options.maxBounds); 9254 + } 9255 + if (map2.options.worldCopyJump) { 9256 + var newLatLng = map2.wrapLatLng(map2.unproject(map2.project(map2.getCenter()).add(offset))); 9257 + map2.panTo(newLatLng); 9258 + } else { 9259 + map2.panBy(offset); 9260 + } 9261 + } 9262 + } else if (key in this._zoomKeys) { 9263 + map2.setZoom(map2.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]); 9264 + } else if (key === 27 && map2._popup && map2._popup.options.closeOnEscapeKey) { 9265 + map2.closePopup(); 9266 + } else { 9267 + return; 9268 + } 9269 + stop(e); 9270 + } 9271 + }); 9272 + Map2.addInitHook("addHandler", "keyboard", Keyboard); 9273 + Map2.mergeOptions({ 9274 + // @section Mouse wheel options 9275 + // @option scrollWheelZoom: Boolean|String = true 9276 + // Whether the map can be zoomed by using the mouse wheel. If passed `'center'`, 9277 + // it will zoom to the center of the view regardless of where the mouse was. 9278 + scrollWheelZoom: true, 9279 + // @option wheelDebounceTime: Number = 40 9280 + // Limits the rate at which a wheel can fire (in milliseconds). By default 9281 + // user can't zoom via wheel more often than once per 40 ms. 9282 + wheelDebounceTime: 40, 9283 + // @option wheelPxPerZoomLevel: Number = 60 9284 + // How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta)) 9285 + // mean a change of one full zoom level. Smaller values will make wheel-zooming 9286 + // faster (and vice versa). 9287 + wheelPxPerZoomLevel: 60 9288 + }); 9289 + var ScrollWheelZoom = Handler.extend({ 9290 + addHooks: function() { 9291 + on(this._map._container, "wheel", this._onWheelScroll, this); 9292 + this._delta = 0; 9293 + }, 9294 + removeHooks: function() { 9295 + off(this._map._container, "wheel", this._onWheelScroll, this); 9296 + }, 9297 + _onWheelScroll: function(e) { 9298 + var delta = getWheelDelta(e); 9299 + var debounce2 = this._map.options.wheelDebounceTime; 9300 + this._delta += delta; 9301 + this._lastMousePos = this._map.mouseEventToContainerPoint(e); 9302 + if (!this._startTime) { 9303 + this._startTime = +/* @__PURE__ */ new Date(); 9304 + } 9305 + var left = Math.max(debounce2 - (+/* @__PURE__ */ new Date() - this._startTime), 0); 9306 + clearTimeout(this._timer); 9307 + this._timer = setTimeout(bind(this._performZoom, this), left); 9308 + stop(e); 9309 + }, 9310 + _performZoom: function() { 9311 + var map2 = this._map, zoom2 = map2.getZoom(), snap = this._map.options.zoomSnap || 0; 9312 + map2._stop(); 9313 + var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4), d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2, d4 = snap ? Math.ceil(d3 / snap) * snap : d3, delta = map2._limitZoom(zoom2 + (this._delta > 0 ? d4 : -d4)) - zoom2; 9314 + this._delta = 0; 9315 + this._startTime = null; 9316 + if (!delta) { 9317 + return; 9318 + } 9319 + if (map2.options.scrollWheelZoom === "center") { 9320 + map2.setZoom(zoom2 + delta); 9321 + } else { 9322 + map2.setZoomAround(this._lastMousePos, zoom2 + delta); 9323 + } 9324 + } 9325 + }); 9326 + Map2.addInitHook("addHandler", "scrollWheelZoom", ScrollWheelZoom); 9327 + var tapHoldDelay = 600; 9328 + Map2.mergeOptions({ 9329 + // @section Touch interaction options 9330 + // @option tapHold: Boolean 9331 + // Enables simulation of `contextmenu` event, default is `true` for mobile Safari. 9332 + tapHold: Browser.touchNative && Browser.safari && Browser.mobile, 9333 + // @option tapTolerance: Number = 15 9334 + // The max number of pixels a user can shift his finger during touch 9335 + // for it to be considered a valid tap. 9336 + tapTolerance: 15 9337 + }); 9338 + var TapHold = Handler.extend({ 9339 + addHooks: function() { 9340 + on(this._map._container, "touchstart", this._onDown, this); 9341 + }, 9342 + removeHooks: function() { 9343 + off(this._map._container, "touchstart", this._onDown, this); 9344 + }, 9345 + _onDown: function(e) { 9346 + clearTimeout(this._holdTimeout); 9347 + if (e.touches.length !== 1) { 9348 + return; 9349 + } 9350 + var first = e.touches[0]; 9351 + this._startPos = this._newPos = new Point(first.clientX, first.clientY); 9352 + this._holdTimeout = setTimeout(bind(function() { 9353 + this._cancel(); 9354 + if (!this._isTapValid()) { 9355 + return; 9356 + } 9357 + on(document, "touchend", preventDefault); 9358 + on(document, "touchend touchcancel", this._cancelClickPrevent); 9359 + this._simulateEvent("contextmenu", first); 9360 + }, this), tapHoldDelay); 9361 + on(document, "touchend touchcancel contextmenu", this._cancel, this); 9362 + on(document, "touchmove", this._onMove, this); 9363 + }, 9364 + _cancelClickPrevent: function cancelClickPrevent() { 9365 + off(document, "touchend", preventDefault); 9366 + off(document, "touchend touchcancel", cancelClickPrevent); 9367 + }, 9368 + _cancel: function() { 9369 + clearTimeout(this._holdTimeout); 9370 + off(document, "touchend touchcancel contextmenu", this._cancel, this); 9371 + off(document, "touchmove", this._onMove, this); 9372 + }, 9373 + _onMove: function(e) { 9374 + var first = e.touches[0]; 9375 + this._newPos = new Point(first.clientX, first.clientY); 9376 + }, 9377 + _isTapValid: function() { 9378 + return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance; 9379 + }, 9380 + _simulateEvent: function(type, e) { 9381 + var simulatedEvent = new MouseEvent(type, { 9382 + bubbles: true, 9383 + cancelable: true, 9384 + view: window, 9385 + // detail: 1, 9386 + screenX: e.screenX, 9387 + screenY: e.screenY, 9388 + clientX: e.clientX, 9389 + clientY: e.clientY 9390 + // button: 2, 9391 + // buttons: 2 9392 + }); 9393 + simulatedEvent._simulated = true; 9394 + e.target.dispatchEvent(simulatedEvent); 9395 + } 9396 + }); 9397 + Map2.addInitHook("addHandler", "tapHold", TapHold); 9398 + Map2.mergeOptions({ 9399 + // @section Touch interaction options 9400 + // @option touchZoom: Boolean|String = * 9401 + // Whether the map can be zoomed by touch-dragging with two fingers. If 9402 + // passed `'center'`, it will zoom to the center of the view regardless of 9403 + // where the touch events (fingers) were. Enabled for touch-capable web 9404 + // browsers. 9405 + touchZoom: Browser.touch, 9406 + // @option bounceAtZoomLimits: Boolean = true 9407 + // Set it to false if you don't want the map to zoom beyond min/max zoom 9408 + // and then bounce back when pinch-zooming. 9409 + bounceAtZoomLimits: true 9410 + }); 9411 + var TouchZoom = Handler.extend({ 9412 + addHooks: function() { 9413 + addClass(this._map._container, "leaflet-touch-zoom"); 9414 + on(this._map._container, "touchstart", this._onTouchStart, this); 9415 + }, 9416 + removeHooks: function() { 9417 + removeClass(this._map._container, "leaflet-touch-zoom"); 9418 + off(this._map._container, "touchstart", this._onTouchStart, this); 9419 + }, 9420 + _onTouchStart: function(e) { 9421 + var map2 = this._map; 9422 + if (!e.touches || e.touches.length !== 2 || map2._animatingZoom || this._zooming) { 9423 + return; 9424 + } 9425 + var p1 = map2.mouseEventToContainerPoint(e.touches[0]), p2 = map2.mouseEventToContainerPoint(e.touches[1]); 9426 + this._centerPoint = map2.getSize()._divideBy(2); 9427 + this._startLatLng = map2.containerPointToLatLng(this._centerPoint); 9428 + if (map2.options.touchZoom !== "center") { 9429 + this._pinchStartLatLng = map2.containerPointToLatLng(p1.add(p2)._divideBy(2)); 9430 + } 9431 + this._startDist = p1.distanceTo(p2); 9432 + this._startZoom = map2.getZoom(); 9433 + this._moved = false; 9434 + this._zooming = true; 9435 + map2._stop(); 9436 + on(document, "touchmove", this._onTouchMove, this); 9437 + on(document, "touchend touchcancel", this._onTouchEnd, this); 9438 + preventDefault(e); 9439 + }, 9440 + _onTouchMove: function(e) { 9441 + if (!e.touches || e.touches.length !== 2 || !this._zooming) { 9442 + return; 9443 + } 9444 + var map2 = this._map, p1 = map2.mouseEventToContainerPoint(e.touches[0]), p2 = map2.mouseEventToContainerPoint(e.touches[1]), scale2 = p1.distanceTo(p2) / this._startDist; 9445 + this._zoom = map2.getScaleZoom(scale2, this._startZoom); 9446 + if (!map2.options.bounceAtZoomLimits && (this._zoom < map2.getMinZoom() && scale2 < 1 || this._zoom > map2.getMaxZoom() && scale2 > 1)) { 9447 + this._zoom = map2._limitZoom(this._zoom); 9448 + } 9449 + if (map2.options.touchZoom === "center") { 9450 + this._center = this._startLatLng; 9451 + if (scale2 === 1) { 9452 + return; 9453 + } 9454 + } else { 9455 + var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint); 9456 + if (scale2 === 1 && delta.x === 0 && delta.y === 0) { 9457 + return; 9458 + } 9459 + this._center = map2.unproject(map2.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom); 9460 + } 9461 + if (!this._moved) { 9462 + map2._moveStart(true, false); 9463 + this._moved = true; 9464 + } 9465 + cancelAnimFrame(this._animRequest); 9466 + var moveFn = bind(map2._move, map2, this._center, this._zoom, { pinch: true, round: false }, void 0); 9467 + this._animRequest = requestAnimFrame(moveFn, this, true); 9468 + preventDefault(e); 9469 + }, 9470 + _onTouchEnd: function() { 9471 + if (!this._moved || !this._zooming) { 9472 + this._zooming = false; 9473 + return; 9474 + } 9475 + this._zooming = false; 9476 + cancelAnimFrame(this._animRequest); 9477 + off(document, "touchmove", this._onTouchMove, this); 9478 + off(document, "touchend touchcancel", this._onTouchEnd, this); 9479 + if (this._map.options.zoomAnimation) { 9480 + this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap); 9481 + } else { 9482 + this._map._resetView(this._center, this._map._limitZoom(this._zoom)); 9483 + } 9484 + } 9485 + }); 9486 + Map2.addInitHook("addHandler", "touchZoom", TouchZoom); 9487 + Map2.BoxZoom = BoxZoom; 9488 + Map2.DoubleClickZoom = DoubleClickZoom; 9489 + Map2.Drag = Drag; 9490 + Map2.Keyboard = Keyboard; 9491 + Map2.ScrollWheelZoom = ScrollWheelZoom; 9492 + Map2.TapHold = TapHold; 9493 + Map2.TouchZoom = TouchZoom; 9494 + exports2.Bounds = Bounds; 9495 + exports2.Browser = Browser; 9496 + exports2.CRS = CRS; 9497 + exports2.Canvas = Canvas; 9498 + exports2.Circle = Circle; 9499 + exports2.CircleMarker = CircleMarker; 9500 + exports2.Class = Class; 9501 + exports2.Control = Control; 9502 + exports2.DivIcon = DivIcon; 9503 + exports2.DivOverlay = DivOverlay; 9504 + exports2.DomEvent = DomEvent; 9505 + exports2.DomUtil = DomUtil; 9506 + exports2.Draggable = Draggable; 9507 + exports2.Evented = Evented; 9508 + exports2.FeatureGroup = FeatureGroup; 9509 + exports2.GeoJSON = GeoJSON; 9510 + exports2.GridLayer = GridLayer; 9511 + exports2.Handler = Handler; 9512 + exports2.Icon = Icon; 9513 + exports2.ImageOverlay = ImageOverlay; 9514 + exports2.LatLng = LatLng; 9515 + exports2.LatLngBounds = LatLngBounds; 9516 + exports2.Layer = Layer; 9517 + exports2.LayerGroup = LayerGroup; 9518 + exports2.LineUtil = LineUtil; 9519 + exports2.Map = Map2; 9520 + exports2.Marker = Marker; 9521 + exports2.Mixin = Mixin; 9522 + exports2.Path = Path; 9523 + exports2.Point = Point; 9524 + exports2.PolyUtil = PolyUtil; 9525 + exports2.Polygon = Polygon; 9526 + exports2.Polyline = Polyline; 9527 + exports2.Popup = Popup; 9528 + exports2.PosAnimation = PosAnimation; 9529 + exports2.Projection = index; 9530 + exports2.Rectangle = Rectangle; 9531 + exports2.Renderer = Renderer; 9532 + exports2.SVG = SVG; 9533 + exports2.SVGOverlay = SVGOverlay; 9534 + exports2.TileLayer = TileLayer; 9535 + exports2.Tooltip = Tooltip; 9536 + exports2.Transformation = Transformation; 9537 + exports2.Util = Util; 9538 + exports2.VideoOverlay = VideoOverlay; 9539 + exports2.bind = bind; 9540 + exports2.bounds = toBounds; 9541 + exports2.canvas = canvas; 9542 + exports2.circle = circle; 9543 + exports2.circleMarker = circleMarker2; 9544 + exports2.control = control; 9545 + exports2.divIcon = divIcon2; 9546 + exports2.extend = extend; 9547 + exports2.featureGroup = featureGroup2; 9548 + exports2.geoJSON = geoJSON; 9549 + exports2.geoJson = geoJson; 9550 + exports2.gridLayer = gridLayer; 9551 + exports2.icon = icon; 9552 + exports2.imageOverlay = imageOverlay; 9553 + exports2.latLng = toLatLng; 9554 + exports2.latLngBounds = toLatLngBounds; 9555 + exports2.layerGroup = layerGroup2; 9556 + exports2.map = createMap2; 9557 + exports2.marker = marker2; 9558 + exports2.point = toPoint; 9559 + exports2.polygon = polygon; 9560 + exports2.polyline = polyline; 9561 + exports2.popup = popup; 9562 + exports2.rectangle = rectangle; 9563 + exports2.setOptions = setOptions; 9564 + exports2.stamp = stamp; 9565 + exports2.svg = svg; 9566 + exports2.svgOverlay = svgOverlay; 9567 + exports2.tileLayer = tileLayer2; 9568 + exports2.tooltip = tooltip; 9569 + exports2.transformation = toTransformation; 9570 + exports2.version = version; 9571 + exports2.videoOverlay = videoOverlay; 9572 + var oldL = window.L; 9573 + exports2.noConflict = function() { 9574 + window.L = oldL; 9575 + return this; 9576 + }; 9577 + window.L = exports2; 9578 + }); 9579 + } 9580 + }); 9581 + 19 9582 // main.ts 20 9583 var main_exports = {}; 21 9584 __export(main_exports, { ··· 43 9606 december: 11 44 9607 }; 45 9608 var URL_RE = /^https?:\/\/\S+$/; 9609 + var GEO_RE = /^geo:\s*(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/; 46 9610 var DATE_WEEKDAY_DD_MONTH_YYYY = /^(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)\s+(\d{1,2})\s+(\w+)\s+(\d{4})/i; 47 9611 var DATE_WEEKDAY_COMMA_MONTH_DD = /^(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday),?\s+(\w+)\s+(\d{1,2})(?:,?\s+(\d{4}))?(?:,?\s+(.+))?/i; 48 9612 var DATE_MONTH_DD_YYYY = /^(\w+)\s+(\d{1,2})(?:,?\s+(\d{4}))?/i; ··· 125 9689 const events = []; 126 9690 const blocks = []; 127 9691 let currentBlock = null; 128 - for (const line of lines) { 9692 + for (let i = 0; i < lines.length; i++) { 9693 + const line = lines[i]; 129 9694 if (/^[*\-]\s/.test(line)) { 130 - currentBlock = [line.replace(/^[*\-]\s+/, "").trim()]; 9695 + currentBlock = { 9696 + lines: [line.replace(/^[*\-]\s+/, "").trim()], 9697 + startLine: i, 9698 + endLine: i 9699 + }; 131 9700 blocks.push(currentBlock); 132 9701 } else if (currentBlock && /^\t[*\-]\s/.test(line)) { 133 - currentBlock.push(line.replace(/^\t[*\-]\s+/, "").trim()); 9702 + currentBlock.lines.push(line.replace(/^\t[*\-]\s+/, "").trim()); 9703 + currentBlock.endLine = i; 134 9704 } else if (currentBlock && /^\s{2,}[*\-]\s/.test(line)) { 135 - currentBlock.push(line.replace(/^\s+[*\-]\s+/, "").trim()); 9705 + currentBlock.lines.push(line.replace(/^\s+[*\-]\s+/, "").trim()); 9706 + currentBlock.endLine = i; 136 9707 } 137 9708 } 138 9709 for (const block of blocks) { 139 - if (block.length === 0) 9710 + if (block.lines.length === 0) 140 9711 continue; 141 - const firstLine = block[0]; 9712 + const firstLine = block.lines[0]; 142 9713 if (!firstLine) 143 9714 continue; 144 - const event = { soldOut: false }; 9715 + const event = { 9716 + soldOut: false, 9717 + startLine: block.startLine, 9718 + endLine: block.endLine 9719 + }; 145 9720 if (URL_RE.test(firstLine)) { 146 9721 event.url = firstLine; 147 9722 } else { ··· 149 9724 event.title = title; 150 9725 event.soldOut = soldOut; 151 9726 } 152 - const subs = block.slice(1); 9727 + const subs = block.lines.slice(1); 153 9728 let dateFound = false; 154 9729 for (const sub of subs) { 155 9730 if (!sub) 156 9731 continue; 9732 + const geoMatch = sub.match(GEO_RE); 9733 + if (geoMatch) { 9734 + event.lat = parseFloat(geoMatch[1]); 9735 + event.lng = parseFloat(geoMatch[2]); 9736 + continue; 9737 + } 157 9738 if (!dateFound) { 158 9739 const parsed = tryParseDate(sub); 159 9740 if (parsed) { ··· 170 9751 continue; 171 9752 } 172 9753 if (/\[.*\]\(.*\)/.test(sub)) { 173 - const links = extractMarkdownLinks(sub); 9754 + const allParts = extractMarkdownLinks(sub); 9755 + const links = allParts.filter((p) => p.url); 174 9756 if (links.length >= 1) { 175 9757 event.venue = links[0].text; 176 9758 event.venueUrl = links[0].url; ··· 214 9796 return (date.getDay() + 6) % 7; 215 9797 } 216 9798 function eventsByDay(events, year, month) { 217 - const map = /* @__PURE__ */ new Map(); 9799 + const map2 = /* @__PURE__ */ new Map(); 218 9800 for (const ev of events) { 219 9801 if (ev.date.getFullYear() === year && ev.date.getMonth() === month) { 220 9802 const day = ev.date.getDate(); 221 - if (!map.has(day)) 222 - map.set(day, []); 223 - map.get(day).push(ev); 9803 + if (!map2.has(day)) 9804 + map2.set(day, []); 9805 + map2.get(day).push(ev); 224 9806 } 225 9807 } 226 - return map; 9808 + return map2; 227 9809 } 228 9810 function createPopover(container, ev, anchor) { 229 9811 const popover = container.createDiv({ cls: "cal-popover" }); ··· 288 9870 popover.style.left = `${left}px`; 289 9871 }); 290 9872 } 291 - function renderCalendar(container, currentMonth, events, callbacks) { 9873 + function renderCalendar(container, currentMonth, events, callbacks, selectedEvent) { 292 9874 container.empty(); 293 9875 container.addClass("cal-container"); 294 9876 const year = currentMonth.getFullYear(); ··· 315 9897 activePopover = null; 316 9898 } 317 9899 }; 9900 + const chipMap = /* @__PURE__ */ new Map(); 9901 + function eventKey(ev) { 9902 + return `${ev.title}::${ev.date.getTime()}`; 9903 + } 318 9904 for (let i = 0; i < startOffset; i++) { 319 9905 grid.createDiv({ cls: "cal-cell cal-cell-empty" }); 320 9906 } ··· 326 9912 cell.addClass("cal-cell-has-events"); 327 9913 const eventsContainer = cell.createDiv({ cls: "cal-cell-events" }); 328 9914 for (const ev of eventsForDay) { 329 - const chip = eventsContainer.createDiv({ 330 - cls: `cal-event-chip${ev.soldOut ? " cal-event-sold-out" : ""}`, 331 - text: ev.title 9915 + const isSelected = selectedEvent && eventKey(ev) === eventKey(selectedEvent); 9916 + const cls = [ 9917 + "cal-event-chip", 9918 + ev.soldOut ? "cal-event-sold-out" : "", 9919 + isSelected ? "cal-event-selected" : "" 9920 + ].filter(Boolean).join(" "); 9921 + const chip = eventsContainer.createDiv({ cls, text: ev.title }); 9922 + chipMap.set(eventKey(ev), chip); 9923 + chip.addEventListener("click", (e) => { 9924 + e.stopPropagation(); 9925 + container.querySelectorAll(".cal-event-selected").forEach( 9926 + (el) => el.classList.remove("cal-event-selected") 9927 + ); 9928 + chip.classList.add("cal-event-selected"); 9929 + callbacks.onEventClick(ev); 332 9930 }); 333 9931 chip.addEventListener("mouseenter", () => { 334 9932 removePopover(); ··· 358 9956 removePopover(); 359 9957 } 360 9958 }); 9959 + if (selectedEvent) { 9960 + const selectedChip = chipMap.get(eventKey(selectedEvent)); 9961 + if (selectedChip) { 9962 + requestAnimationFrame(() => selectedChip.scrollIntoView({ block: "nearest" })); 9963 + } 9964 + } 9965 + const controller = { 9966 + selectEvent(event) { 9967 + container.querySelectorAll(".cal-event-selected").forEach( 9968 + (el) => el.classList.remove("cal-event-selected") 9969 + ); 9970 + if (!event) 9971 + return false; 9972 + const evYear = event.date.getFullYear(); 9973 + const evMonth = event.date.getMonth(); 9974 + if (evYear !== year || evMonth !== month) { 9975 + return true; 9976 + } 9977 + const chip = chipMap.get(eventKey(event)); 9978 + if (chip) { 9979 + chip.classList.add("cal-event-selected"); 9980 + chip.scrollIntoView({ block: "nearest" }); 9981 + } 9982 + return false; 9983 + } 9984 + }; 9985 + return controller; 9986 + } 9987 + 9988 + // mapRenderer.ts 9989 + var L2 = __toESM(require_leaflet_src()); 9990 + function getAccentColor() { 9991 + return getComputedStyle(document.body).getPropertyValue("--interactive-accent").trim() || "#7f6df2"; 9992 + } 9993 + function darkenHex(hex, factor) { 9994 + const h = hex.replace("#", ""); 9995 + const r = Math.max(0, Math.round(parseInt(h.slice(0, 2), 16) * (1 - factor))); 9996 + const g = Math.max(0, Math.round(parseInt(h.slice(2, 4), 16) * (1 - factor))); 9997 + const b = Math.max(0, Math.round(parseInt(h.slice(4, 6), 16) * (1 - factor))); 9998 + return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; 9999 + } 10000 + function createPinIcon(soldOut) { 10001 + const accent = getAccentColor(); 10002 + const fill = soldOut ? "#999" : accent; 10003 + const stroke = soldOut ? "#777" : darkenHex(accent, 0.25); 10004 + const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="40" viewBox="0 0 28 40"><path d="M14 0C6.3 0 0 6.3 0 14c0 10.5 14 26 14 26s14-15.5 14-26C28 6.3 21.7 0 14 0z" fill="${fill}" stroke="${stroke}" stroke-width="1.5"/><circle cx="14" cy="14" r="5" fill="white" opacity="0.85"/></svg>`; 10005 + return L2.divIcon({ 10006 + html: svg, 10007 + className: "cal-pin-icon", 10008 + iconSize: [28, 40], 10009 + iconAnchor: [14, 40], 10010 + popupAnchor: [0, -36] 10011 + }); 10012 + } 10013 + var WATERCOLOR_URL = "https://watercolormaps.collection.cooperhewitt.org/tile/watercolor/{z}/{x}/{y}.jpg"; 10014 + var LABELS_URL = "https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png"; 10015 + var TILE_ATTRIBUTION = 'Map tiles by <a href="https://stamen.com/">Stamen Design</a>, hosted by <a href="https://collection.cooperhewitt.org/">Cooper Hewitt</a>. Labels by <a href="https://carto.com/">CARTO</a>. Data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'; 10016 + function popupContent(events) { 10017 + return events.map((ev) => { 10018 + const title = ev.url ? `<a href="${ev.url}" target="_blank">${escapeHtml(ev.title)}</a>` : escapeHtml(ev.title); 10019 + const soldOut = ev.soldOut ? ' <span class="cal-sold-out">(Sold out)</span>' : ""; 10020 + const date = ev.date.toLocaleDateString("en-US", { 10021 + weekday: "short", 10022 + month: "short", 10023 + day: "numeric" 10024 + }); 10025 + const time = ev.rawTime ? `, ${escapeHtml(ev.rawTime)}` : ""; 10026 + const venue = ev.venue ? `<br><span class="cal-popup-venue">${escapeHtml(ev.venue)}</span>` : ""; 10027 + return `<div class="cal-popup-event"><strong>${title}</strong>${soldOut}<br>${date}${time}${venue}</div>`; 10028 + }).join(""); 10029 + } 10030 + function escapeHtml(s) { 10031 + return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); 10032 + } 10033 + function groupByLocation(events) { 10034 + const groups = /* @__PURE__ */ new Map(); 10035 + for (const ev of events) { 10036 + if (ev.lat === void 0 || ev.lng === void 0) 10037 + continue; 10038 + const key = `${ev.lat.toFixed(6)},${ev.lng.toFixed(6)}`; 10039 + if (!groups.has(key)) 10040 + groups.set(key, []); 10041 + groups.get(key).push(ev); 10042 + } 10043 + return groups; 10044 + } 10045 + function createMap(container, events, callbacks) { 10046 + if (!document.getElementById("leaflet-css")) { 10047 + const link = document.createElement("style"); 10048 + link.id = "leaflet-css"; 10049 + link.textContent = leafletCss(); 10050 + document.head.appendChild(link); 10051 + } 10052 + const mapDiv = container.createDiv({ cls: "cal-map" }); 10053 + const map2 = L2.map(mapDiv, { 10054 + zoomControl: true, 10055 + attributionControl: true 10056 + }); 10057 + L2.tileLayer(WATERCOLOR_URL, { 10058 + attribution: TILE_ATTRIBUTION, 10059 + maxZoom: 19 10060 + }).addTo(map2); 10061 + L2.tileLayer(LABELS_URL, { 10062 + maxZoom: 19, 10063 + subdomains: "abcd", 10064 + pane: "overlayPane" 10065 + }).addTo(map2); 10066 + const markerLayer = L2.layerGroup().addTo(map2); 10067 + let markerMap = /* @__PURE__ */ new Map(); 10068 + let selectedMarker = null; 10069 + let highlightCircle = null; 10070 + function clearHighlight() { 10071 + if (highlightCircle) { 10072 + highlightCircle.remove(); 10073 + highlightCircle = null; 10074 + } 10075 + selectedMarker = null; 10076 + } 10077 + function highlightMarker(marker2) { 10078 + clearHighlight(); 10079 + selectedMarker = marker2; 10080 + const latlng = marker2.getLatLng(); 10081 + highlightCircle = L2.circleMarker(latlng, { 10082 + radius: 18, 10083 + color: "var(--interactive-accent, #7b6cd9)", 10084 + fillColor: "var(--interactive-accent, #7b6cd9)", 10085 + fillOpacity: 0.2, 10086 + weight: 2, 10087 + className: "cal-marker-highlight" 10088 + }).addTo(map2); 10089 + } 10090 + function buildMarkers(evts) { 10091 + markerLayer.clearLayers(); 10092 + markerMap.clear(); 10093 + clearHighlight(); 10094 + const groups = groupByLocation(evts); 10095 + for (const [key, groupEvents] of groups) { 10096 + const [lat, lng] = key.split(",").map(Number); 10097 + const allSoldOut = groupEvents.every((e) => e.soldOut); 10098 + const marker2 = L2.marker([lat, lng], { 10099 + icon: createPinIcon(allSoldOut) 10100 + }); 10101 + marker2.bindPopup(popupContent(groupEvents), { 10102 + maxWidth: 240, 10103 + className: "cal-map-popup" 10104 + }); 10105 + marker2.on("click", () => { 10106 + highlightMarker(marker2); 10107 + callbacks.onMarkerClick(groupEvents[0]); 10108 + }); 10109 + marker2.addTo(markerLayer); 10110 + markerMap.set(key, { marker: marker2, events: groupEvents }); 10111 + } 10112 + } 10113 + buildMarkers(events); 10114 + function doFitBounds() { 10115 + const allMarkers = Array.from(markerMap.values()).map((m) => m.marker); 10116 + if (allMarkers.length === 0) { 10117 + map2.setView([20, 0], 2); 10118 + return; 10119 + } 10120 + if (allMarkers.length === 1) { 10121 + map2.setView(allMarkers[0].getLatLng(), 13); 10122 + return; 10123 + } 10124 + const group = L2.featureGroup(allMarkers); 10125 + map2.fitBounds(group.getBounds().pad(0.15)); 10126 + } 10127 + setTimeout(() => { 10128 + map2.invalidateSize(); 10129 + doFitBounds(); 10130 + }, 50); 10131 + const controller = { 10132 + updateMarkers(evts, fit = true) { 10133 + buildMarkers(evts); 10134 + if (fit) { 10135 + doFitBounds(); 10136 + } 10137 + }, 10138 + selectEvent(event) { 10139 + if (!event) { 10140 + clearHighlight(); 10141 + map2.closePopup(); 10142 + return; 10143 + } 10144 + if (event.lat === void 0 || event.lng === void 0) 10145 + return; 10146 + const key = `${event.lat.toFixed(6)},${event.lng.toFixed(6)}`; 10147 + const entry = markerMap.get(key); 10148 + if (entry) { 10149 + highlightMarker(entry.marker); 10150 + entry.marker.openPopup(); 10151 + map2.panTo(entry.marker.getLatLng(), { animate: true }); 10152 + } 10153 + }, 10154 + fitBounds() { 10155 + doFitBounds(); 10156 + }, 10157 + invalidateSize() { 10158 + map2.invalidateSize(); 10159 + }, 10160 + destroy() { 10161 + map2.remove(); 10162 + } 10163 + }; 10164 + return controller; 10165 + } 10166 + function leafletCss() { 10167 + return ` 10168 + /* Leaflet 1.9.4 \u2014 essential styles */ 10169 + .leaflet-pane, .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow, 10170 + .leaflet-tile-container, .leaflet-pane > svg, .leaflet-pane > canvas, 10171 + .leaflet-zoom-box, .leaflet-image-layer, .leaflet-layer { 10172 + position: absolute; left: 0; top: 0; 10173 + } 10174 + .leaflet-container { overflow: hidden; } 10175 + .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow { user-select: none; -webkit-user-select: none; } 10176 + .leaflet-tile::selection { background: transparent; } 10177 + .leaflet-safari .leaflet-tile { image-rendering: -webkit-optimize-contrast; } 10178 + .leaflet-safari .leaflet-tile-container { width: 1600px; height: 1600px; -webkit-transform-origin: 0 0; } 10179 + .leaflet-marker-icon, .leaflet-marker-shadow { display: block; } 10180 + .leaflet-container .leaflet-overlay-pane svg { max-width: none !important; max-height: none !important; } 10181 + .leaflet-container .leaflet-marker-pane img, 10182 + .leaflet-container .leaflet-shadow-pane img, 10183 + .leaflet-container .leaflet-tile-pane img, 10184 + .leaflet-container img.leaflet-image-layer, 10185 + .leaflet-container .leaflet-tile { max-width: none !important; max-height: none !important; width: auto; padding: 0; } 10186 + .leaflet-container.leaflet-touch-zoom { touch-action: pan-x pan-y; } 10187 + .leaflet-container.leaflet-touch-drag { touch-action: none; touch-action: pinch-zoom; } 10188 + .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { touch-action: none; } 10189 + .leaflet-container { -webkit-tap-highlight-color: transparent; } 10190 + .leaflet-container a { -webkit-tap-highlight-color: rgba(51,181,229,0.4); } 10191 + .leaflet-tile { filter: inherit; visibility: hidden; } 10192 + .leaflet-tile-loaded { visibility: inherit; } 10193 + .leaflet-zoom-box { width: 0; height: 0; box-sizing: border-box; z-index: 800; } 10194 + .leaflet-overlay-pane svg { -moz-user-select: none; } 10195 + .leaflet-pane { z-index: 400; } 10196 + .leaflet-tile-pane { z-index: 200; } 10197 + .leaflet-overlay-pane { z-index: 400; } 10198 + .leaflet-shadow-pane { z-index: 500; } 10199 + .leaflet-marker-pane { z-index: 600; } 10200 + .leaflet-tooltip-pane { z-index: 650; } 10201 + .leaflet-popup-pane { z-index: 700; } 10202 + .leaflet-map-pane canvas { z-index: 100; } 10203 + .leaflet-map-pane svg { z-index: 200; } 10204 + .leaflet-vml-shape { width: 1px; height: 1px; } 10205 + .lvml { behavior: url(#default#VML); display: inline-block; position: absolute; } 10206 + .leaflet-control { position: relative; z-index: 800; pointer-events: visiblePainted; pointer-events: auto; } 10207 + .leaflet-top, .leaflet-bottom { position: absolute; z-index: 1000; pointer-events: none; } 10208 + .leaflet-top { top: 0; } 10209 + .leaflet-right { right: 0; } 10210 + .leaflet-bottom { bottom: 0; } 10211 + .leaflet-left { left: 0; } 10212 + .leaflet-control { float: left; clear: both; } 10213 + .leaflet-right .leaflet-control { float: right; } 10214 + .leaflet-top .leaflet-control { margin-top: 10px; } 10215 + .leaflet-bottom .leaflet-control { margin-bottom: 10px; } 10216 + .leaflet-left .leaflet-control { margin-left: 10px; } 10217 + .leaflet-right .leaflet-control { margin-right: 10px; } 10218 + .leaflet-fade-anim .leaflet-popup { opacity: 1; transition: opacity 0.2s linear; } 10219 + .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { opacity: 1; } 10220 + .leaflet-zoom-animated { transform-origin: 0 0; } 10221 + .leaflet-zoom-anim .leaflet-zoom-animated { will-change: transform; transition: transform 0.25s cubic-bezier(0,0,0.25,1); } 10222 + .leaflet-zoom-anim .leaflet-tile, .leaflet-pan-anim .leaflet-tile { transition: none; } 10223 + .leaflet-zoom-anim .leaflet-zoom-hide { visibility: hidden; } 10224 + .leaflet-interactive { cursor: pointer; } 10225 + .leaflet-grab { cursor: grab; } 10226 + .leaflet-crosshair, .leaflet-crosshair .leaflet-interactive { cursor: crosshair; } 10227 + .leaflet-popup-pane, .leaflet-control { cursor: auto; } 10228 + .leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive, 10229 + .leaflet-dragging .leaflet-marker-draggable { cursor: move; cursor: grabbing; } 10230 + .leaflet-marker-icon, .leaflet-marker-shadow, .leaflet-image-layer, 10231 + .leaflet-pane > svg path, .leaflet-tile-container { pointer-events: none; } 10232 + .leaflet-marker-icon.leaflet-interactive, .leaflet-image-layer.leaflet-interactive, 10233 + .leaflet-pane > svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { pointer-events: visiblePainted; pointer-events: auto; } 10234 + .leaflet-container a.leaflet-active { outline: 2px solid orange; } 10235 + .leaflet-zoom-box { border: 2px dotted #38f; background: rgba(255,255,255,0.5); } 10236 + .leaflet-bar { box-shadow: 0 1px 5px rgba(0,0,0,0.65); border-radius: 4px; } 10237 + .leaflet-bar a, .leaflet-bar a:hover { background-color: #fff; border-bottom: 1px solid #ccc; width: 26px; height: 26px; line-height: 26px; display: block; text-align: center; text-decoration: none; color: black; } 10238 + .leaflet-bar a, .leaflet-control-layers-toggle { background-position: 50% 50%; background-repeat: no-repeat; display: block; } 10239 + .leaflet-bar a:hover, .leaflet-bar a:focus { background-color: #f4f4f4; } 10240 + .leaflet-bar a:first-child { border-top-left-radius: 4px; border-top-right-radius: 4px; } 10241 + .leaflet-bar a:last-child { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-bottom: none; } 10242 + .leaflet-bar a.leaflet-disabled { cursor: default; background-color: #f4f4f4; color: #bbb; } 10243 + .leaflet-touch .leaflet-bar a { width: 30px; height: 30px; line-height: 30px; } 10244 + .leaflet-touch .leaflet-bar a:first-child { border-top-left-radius: 2px; border-top-right-radius: 2px; } 10245 + .leaflet-touch .leaflet-bar a:last-child { border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } 10246 + .leaflet-control-zoom-in, .leaflet-control-zoom-out { font: bold 18px 'Lucida Console', Monaco, monospace; text-indent: 1px; } 10247 + .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { font-size: 22px; } 10248 + .leaflet-control-layers { box-shadow: 0 1px 5px rgba(0,0,0,0.4); background: #fff; border-radius: 5px; } 10249 + .leaflet-control-layers-toggle { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1TA5AVURDsmc22bdu2bdu2bdu2bdu2bZ3v3P3f1t2q7mL6AQBSNG03EMHQF8MwEiNxBmfxMz7FJ/iROI0zOINfCF2RC9lRG8MxFRdwSw0EcBu3cBEXcRt31ECq4AFMI3RFLVRHZ3RBf0zAHJzBDdzEHdzFfTzAQzzCYzzBUzzDc7zASwjBV3iDb/AO3+MHkiSf8dPf4mt8ha/xJb7AF/gcn+FTfIJP8DE+wof4AO/jPbyLd/A23sKbeAOv43W8hlfxCl7Gy3gJL+IFPIdncQ+P8RRP8ARP8BSvwGOAkBm50AsrsR+38BCv4BLO4DTu4h6e4SVe4CXe4CU+wPt4D+/iHbyNt/Am3sDreA2v4hW8jJfwIl7Ac3gWz+AuHuMpnuAJnuI5eMQjP4cLauA+buE6LuMCzuE0TuE8ruEObuMhHuEWHuABnuExnuIRnuAJnuEZ+FfFa3gVr+BlvISX8CJewPN4Ds/iGdzFYzzFEzzBUzzHSwjBV/gG3+I7fI8fCKLkM37+W3yNr/A1vsSX+AKf4zN8ik/wMT7Ch/gA7+M9vIt38DbewsN4C2/iDbyO1/EaXsUreBkv4SW8iBfwPJ7DU7iHx3iKx3iCJ3iGF+CRH+Ey7uEOLuMMzuMiruEGbuI2buMO7uE+HuIhHuMpHuMJnuI5XoJHvIJX8Rpexat4Ba/gZbyEl/ACnsdzeBbP4C4e4yme4Ame4hlewZ8+DJmRC30xAbtxDQ/xGu7gNE7hMu7gLu7jAR7iMZ7gKR7jCZ7hOV6AAG/hTbyBt/AWHsJbeAsP4x28i3fwNt7CG3gdr+E1vIpX8DJewot4Ac/hWTyNu3iEx3j67w8P/z9FWIJUIAAAAABJRU5ErkJggg==); width: 36px; height: 36px; } 10250 + .leaflet-retina .leaflet-control-layers-toggle { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kAAAABJRU5ErkJggg==); background-size: 26px 26px; } 10251 + .leaflet-touch .leaflet-control-layers-toggle { width: 44px; height: 44px; } 10252 + .leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { display: none; } 10253 + .leaflet-control-layers-expanded .leaflet-control-layers-list { display: block; position: relative; } 10254 + .leaflet-control-layers-expanded { padding: 6px 10px 6px 6px; color: #333; background: #fff; } 10255 + .leaflet-control-layers-scrollbar { overflow-y: scroll; overflow-x: hidden; padding-right: 5px; } 10256 + .leaflet-control-layers-selector { margin-top: 2px; position: relative; top: 1px; } 10257 + .leaflet-control-layers label { display: block; font-size: 13px; font-size: 1.08333em; } 10258 + .leaflet-control-layers-separator { height: 0; border-top: 1px solid #ddd; margin: 5px -10px 5px -6px; } 10259 + .leaflet-default-icon-path { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YassW17LFNesR17LWtAnOez/J5tsupervised/cLOGeli2lGZjUtMPail370a57y6u6deuj75NAfO/6fOM0M0SUR4JYAAAAABJRU5ErkJggg==); } 10260 + .leaflet-container .leaflet-control-attribution { background: #fff; background: rgba(255,255,255,0.8); margin: 0; } 10261 + .leaflet-control-attribution, .leaflet-control-scale-line { padding: 0 5px; color: #333; line-height: 1.4; } 10262 + .leaflet-control-attribution a { text-decoration: none; } 10263 + .leaflet-control-attribution a:hover, .leaflet-control-attribution a:focus { text-decoration: underline; } 10264 + .leaflet-attribution-flag { display: none !important; } 10265 + .leaflet-left .leaflet-control-scale { margin-left: 5px; } 10266 + .leaflet-bottom .leaflet-control-scale { margin-bottom: 5px; } 10267 + .leaflet-control-scale-line { border: 2px solid #777; border-top: none; line-height: 1.1; padding: 2px 5px 1px; white-space: nowrap; overflow: hidden; box-sizing: border-box; background: rgba(255,255,255,0.5); } 10268 + .leaflet-control-scale-line:not(:first-child) { border-top: 2px solid #777; border-bottom: none; margin-top: -2px; } 10269 + .leaflet-control-scale-line:not(:first-child):not(:last-child) { border-bottom: 2px solid #777; } 10270 + .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers, 10271 + .leaflet-touch .leaflet-bar { box-shadow: none; } 10272 + .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { border: 2px solid rgba(0,0,0,0.2); background-clip: padding-box; } 10273 + .leaflet-popup { position: absolute; text-align: center; margin-bottom: 20px; } 10274 + .leaflet-popup-content-wrapper { padding: 1px; text-align: left; border-radius: 12px; } 10275 + .leaflet-popup-content { margin: 13px 24px 13px 20px; line-height: 1.3; font-size: 13px; font-size: 1.08333em; min-height: 1px; } 10276 + .leaflet-popup-content p { margin: 17px 0; margin: 1.3em 0; } 10277 + .leaflet-popup-tip-container { width: 40px; height: 20px; position: absolute; left: 50%; margin-top: -1px; margin-left: -20px; overflow: hidden; pointer-events: none; } 10278 + .leaflet-popup-tip { width: 17px; height: 17px; padding: 1px; margin: -10px auto 0; pointer-events: auto; transform: rotate(45deg); } 10279 + .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: white; color: #333; box-shadow: 0 3px 14px rgba(0,0,0,0.4); } 10280 + .leaflet-container a.leaflet-popup-close-button { position: absolute; top: 0; right: 0; border: none; text-align: center; width: 24px; height: 24px; font: 16px/24px Tahoma, Verdana, sans-serif; color: #757575; text-decoration: none; background: transparent; } 10281 + .leaflet-container a.leaflet-popup-close-button:hover, .leaflet-container a.leaflet-popup-close-button:focus { color: #585858; } 10282 + .leaflet-popup-scrolled { overflow: auto; } 10283 + .leaflet-oldie .leaflet-popup-content-wrapper { -ms-zoom: 1; } 10284 + .leaflet-oldie .leaflet-popup-tip { width: 24px; margin: 0 auto; -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); } 10285 + .leaflet-oldie .leaflet-control-zoom, .leaflet-oldie .leaflet-control-layers, 10286 + .leaflet-oldie .leaflet-popup-content-wrapper, .leaflet-oldie .leaflet-popup-tip { border: 1px solid #999; } 10287 + .leaflet-div-icon { background: #fff; border: 1px solid #666; } 10288 + .leaflet-tooltip { position: absolute; padding: 6px; background-color: #fff; border: 1px solid #fff; border-radius: 3px; color: #222; white-space: nowrap; user-select: none; pointer-events: none; box-shadow: 0 1px 3px rgba(0,0,0,0.4); } 10289 + .leaflet-tooltip.leaflet-interactive { cursor: pointer; pointer-events: auto; } 10290 + .leaflet-tooltip-top:before, .leaflet-tooltip-bottom:before, 10291 + .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { position: absolute; pointer-events: none; border: 6px solid transparent; background: transparent; content: ""; } 10292 + .leaflet-tooltip-bottom { margin-top: 6px; } 10293 + .leaflet-tooltip-top { margin-top: -6px; } 10294 + .leaflet-tooltip-bottom:before, .leaflet-tooltip-top:before { left: 50%; margin-left: -6px; } 10295 + .leaflet-tooltip-top:before { bottom: 0; margin-bottom: -12px; border-top-color: #fff; } 10296 + .leaflet-tooltip-bottom:before { top: 0; margin-top: -12px; margin-left: -6px; border-bottom-color: #fff; } 10297 + .leaflet-tooltip-left { margin-left: -6px; } 10298 + .leaflet-tooltip-right { margin-left: 6px; } 10299 + .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { top: 50%; margin-top: -6px; } 10300 + .leaflet-tooltip-left:before { right: 0; margin-right: -12px; border-left-color: #fff; } 10301 + .leaflet-tooltip-right:before { left: 0; margin-left: -12px; border-right-color: #fff; } 10302 + @media print { .leaflet-control { -webkit-print-color-adjust: exact; print-color-adjust: exact; } } 10303 + `; 10304 + } 10305 + 10306 + // geocoder.ts 10307 + var NOMINATIM_URL = "https://nominatim.openstreetmap.org/search"; 10308 + var RATE_LIMIT_MS = 1100; 10309 + function buildQuery(event) { 10310 + const parts = []; 10311 + if (event.venue) 10312 + parts.push(event.venue); 10313 + if (event.location) 10314 + parts.push(event.location); 10315 + if (parts.length === 0) 10316 + return null; 10317 + return parts.join(", "); 10318 + } 10319 + function normalizeQuery(query) { 10320 + return query.toLowerCase().trim(); 10321 + } 10322 + async function fetchGeocode(query) { 10323 + try { 10324 + console.debug(`[CalendarViewer] Geocoding: "${query}"`); 10325 + const params = new URLSearchParams({ 10326 + q: query, 10327 + format: "json", 10328 + limit: "1" 10329 + }); 10330 + const response = await fetch(`${NOMINATIM_URL}?${params}`, { 10331 + headers: { 10332 + "User-Agent": "ObsidianCalendarViewer/1.0" 10333 + } 10334 + }); 10335 + if (!response.ok) { 10336 + console.warn(`[CalendarViewer] Geocoding failed for "${query}": HTTP ${response.status}`); 10337 + return null; 10338 + } 10339 + const results = await response.json(); 10340 + if (results.length > 0) { 10341 + const loc = { 10342 + lat: parseFloat(results[0].lat), 10343 + lng: parseFloat(results[0].lon) 10344 + }; 10345 + console.debug(`[CalendarViewer] Geocoded "${query}" \u2192 ${loc.lat}, ${loc.lng}`); 10346 + return loc; 10347 + } 10348 + console.warn(`[CalendarViewer] Geocoding returned no results for "${query}"`); 10349 + return null; 10350 + } catch (e) { 10351 + console.warn(`[CalendarViewer] Geocoding error for "${query}":`, e); 10352 + return null; 10353 + } 10354 + } 10355 + function sleep(ms) { 10356 + return new Promise((resolve) => setTimeout(resolve, ms)); 10357 + } 10358 + async function geocodeEvents(events, onProgress) { 10359 + const needsGeocoding = []; 10360 + for (const event of events) { 10361 + if (event.lat !== void 0 && event.lng !== void 0) { 10362 + console.debug(`[CalendarViewer] Skipping "${event.title}" \u2014 already has coords`); 10363 + continue; 10364 + } 10365 + const query = buildQuery(event); 10366 + if (!query) { 10367 + console.debug(`[CalendarViewer] Skipping "${event.title}" \u2014 no venue/location`); 10368 + continue; 10369 + } 10370 + needsGeocoding.push({ event, query, key: normalizeQuery(query) }); 10371 + } 10372 + const seen = /* @__PURE__ */ new Set(); 10373 + const unique = []; 10374 + for (const item of needsGeocoding) { 10375 + if (!seen.has(item.key)) { 10376 + seen.add(item.key); 10377 + unique.push(item); 10378 + } 10379 + } 10380 + if (unique.length > 0) { 10381 + console.debug(`[CalendarViewer] Geocoding ${unique.length} unique location(s)...`); 10382 + } 10383 + const newlyGeocoded = []; 10384 + for (let i = 0; i < unique.length; i++) { 10385 + const { query, key } = unique[i]; 10386 + if (i > 0) { 10387 + await sleep(RATE_LIMIT_MS); 10388 + } 10389 + const result = await fetchGeocode(query); 10390 + if (result) { 10391 + for (const item of needsGeocoding) { 10392 + if (item.key === key) { 10393 + item.event.lat = result.lat; 10394 + item.event.lng = result.lng; 10395 + newlyGeocoded.push(item.event); 10396 + } 10397 + } 10398 + } 10399 + onProgress(); 10400 + } 10401 + return newlyGeocoded; 361 10402 } 362 10403 363 10404 // calendarView.ts 364 10405 var VIEW_TYPE_CALENDAR = "calendar-viewer"; 365 10406 var CalendarView = class extends import_obsidian.ItemView { 366 - constructor(leaf) { 10407 + constructor(leaf, plugin) { 367 10408 super(leaf); 368 10409 this.events = []; 10410 + this.selectedEvent = null; 10411 + this.hasUserNavigated = false; 10412 + this.calendarEl = null; 10413 + this.mapEl = null; 10414 + this.calendarController = null; 10415 + this.mapController = null; 10416 + this.resizeObserver = null; 10417 + /** Guard: true while we're writing geo lines back to the doc */ 10418 + this.isWritingGeoLines = false; 10419 + this.eventsFingerprint = ""; 10420 + this.cursorPollInterval = null; 10421 + this.lastCursorLine = -1; 369 10422 /** 370 - * Re-parse the active note and re-render the calendar. 10423 + * Re-parse the active note, geocode events, and re-render everything. 371 10424 */ 372 10425 this.refresh = (0, import_obsidian.debounce)(async () => { 10426 + if (this.isWritingGeoLines) 10427 + return; 373 10428 const file = this.app.workspace.getActiveFile(); 374 10429 if (file) { 375 10430 const content = await this.app.vault.read(file); ··· 380 10435 } else { 381 10436 this.events = []; 382 10437 } 383 - this.render(); 10438 + const newFingerprint = this.computeFingerprint(this.events); 10439 + const eventsChanged = newFingerprint !== this.eventsFingerprint; 10440 + this.eventsFingerprint = newFingerprint; 10441 + this.selectedEvent = null; 10442 + this.renderCalendar(); 10443 + if (eventsChanged) { 10444 + this.initOrUpdateMap(); 10445 + await this.geocodeAndUpdateMap(); 10446 + } 384 10447 }, 300, true); 385 - this.hasUserNavigated = false; 10448 + this.plugin = plugin; 386 10449 this.currentMonth = /* @__PURE__ */ new Date(); 387 10450 this.currentMonth.setDate(1); 388 10451 } ··· 396 10459 return "calendar"; 397 10460 } 398 10461 async onOpen() { 10462 + const content = this.containerEl.children[1]; 10463 + content.empty(); 10464 + content.addClass("cal-view-root"); 10465 + this.calendarEl = content.createDiv({ cls: "cal-pane-top" }); 10466 + this.mapEl = content.createDiv({ cls: "cal-pane-bottom" }); 10467 + this.resizeObserver = new ResizeObserver(() => { 10468 + var _a; 10469 + (_a = this.mapController) == null ? void 0 : _a.invalidateSize(); 10470 + }); 10471 + this.resizeObserver.observe(this.mapEl); 10472 + this.cursorPollInterval = setInterval(() => { 10473 + this.pollCursorPosition(); 10474 + }, 200); 399 10475 await this.refresh(); 400 10476 } 401 10477 async onClose() { 10478 + var _a, _b; 10479 + if (this.cursorPollInterval !== null) { 10480 + clearInterval(this.cursorPollInterval); 10481 + this.cursorPollInterval = null; 10482 + } 10483 + (_a = this.mapController) == null ? void 0 : _a.destroy(); 10484 + this.mapController = null; 10485 + (_b = this.resizeObserver) == null ? void 0 : _b.disconnect(); 10486 + this.resizeObserver = null; 10487 + } 10488 + /** 10489 + * Reset the user-navigated flag when switching notes. 10490 + */ 10491 + resetNavigation() { 10492 + this.hasUserNavigated = false; 10493 + } 10494 + // ── Private ── 10495 + /** 10496 + * Compute a simple fingerprint of the event list (titles + dates). 10497 + * Used to detect whether events actually changed between refreshes. 10498 + */ 10499 + computeFingerprint(events) { 10500 + return events.map((e) => { 10501 + var _a, _b; 10502 + return `${e.title}::${e.date.getTime()}::${(_a = e.venue) != null ? _a : ""}::${(_b = e.location) != null ? _b : ""}`; 10503 + }).join("|"); 402 10504 } 403 10505 jumpToNearestMonth() { 404 10506 const now = /* @__PURE__ */ new Date(); ··· 411 10513 this.currentMonth = new Date(target.date.getFullYear(), target.date.getMonth(), 1); 412 10514 } 413 10515 } 414 - render() { 415 - const container = this.containerEl.children[1]; 416 - renderCalendar(container, this.currentMonth, this.events, { 417 - onPrevMonth: () => { 418 - this.hasUserNavigated = true; 419 - this.currentMonth = new Date( 420 - this.currentMonth.getFullYear(), 421 - this.currentMonth.getMonth() - 1, 422 - 1 423 - ); 424 - this.render(); 10516 + renderCalendar() { 10517 + if (!this.calendarEl) 10518 + return; 10519 + this.calendarController = renderCalendar( 10520 + this.calendarEl, 10521 + this.currentMonth, 10522 + this.events, 10523 + { 10524 + onPrevMonth: () => { 10525 + this.hasUserNavigated = true; 10526 + this.currentMonth = new Date( 10527 + this.currentMonth.getFullYear(), 10528 + this.currentMonth.getMonth() - 1, 10529 + 1 10530 + ); 10531 + this.renderCalendar(); 10532 + }, 10533 + onNextMonth: () => { 10534 + this.hasUserNavigated = true; 10535 + this.currentMonth = new Date( 10536 + this.currentMonth.getFullYear(), 10537 + this.currentMonth.getMonth() + 1, 10538 + 1 10539 + ); 10540 + this.renderCalendar(); 10541 + }, 10542 + onEventClick: (event) => { 10543 + var _a; 10544 + this.selectedEvent = event; 10545 + (_a = this.mapController) == null ? void 0 : _a.selectEvent(event); 10546 + this.scrollEditorToEvent(event); 10547 + } 425 10548 }, 426 - onNextMonth: () => { 427 - this.hasUserNavigated = true; 428 - this.currentMonth = new Date( 429 - this.currentMonth.getFullYear(), 430 - this.currentMonth.getMonth() + 1, 431 - 1 432 - ); 433 - this.render(); 10549 + this.selectedEvent 10550 + ); 10551 + } 10552 + initOrUpdateMap() { 10553 + if (!this.mapEl) 10554 + return; 10555 + if (this.mapController) { 10556 + this.mapController.updateMarkers(this.events); 10557 + } else { 10558 + this.mapController = createMap(this.mapEl, this.events, { 10559 + onMarkerClick: (event) => { 10560 + this.selectedEvent = event; 10561 + const evMonth = event.date.getMonth(); 10562 + const evYear = event.date.getFullYear(); 10563 + if (evYear !== this.currentMonth.getFullYear() || evMonth !== this.currentMonth.getMonth()) { 10564 + this.hasUserNavigated = true; 10565 + this.currentMonth = new Date(evYear, evMonth, 1); 10566 + } 10567 + this.renderCalendar(); 10568 + this.scrollEditorToEvent(event); 10569 + } 10570 + }); 10571 + } 10572 + } 10573 + async geocodeAndUpdateMap() { 10574 + if (this.events.length === 0) 10575 + return; 10576 + const newlyGeocoded = await geocodeEvents( 10577 + this.events, 10578 + // onProgress: update the map with new markers but don't re-fit bounds 10579 + () => { 10580 + var _a; 10581 + (_a = this.mapController) == null ? void 0 : _a.updateMarkers(this.events, false); 434 10582 } 435 - }); 10583 + ); 10584 + if (newlyGeocoded.length > 0) { 10585 + await this.writeGeoLinesToDoc(newlyGeocoded); 10586 + } 10587 + } 10588 + /** 10589 + * Poll the active editor's cursor position and sync selection 10590 + * to the event whose block contains the cursor line. 10591 + */ 10592 + pollCursorPosition() { 10593 + const mdView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); 10594 + if (!mdView) 10595 + return; 10596 + const cursor = mdView.editor.getCursor(); 10597 + const line = cursor.line; 10598 + if (line === this.lastCursorLine) 10599 + return; 10600 + this.lastCursorLine = line; 10601 + this.selectEventByLine(line); 436 10602 } 437 10603 /** 438 - * Reset the user-navigated flag so the view jumps to the 439 - * relevant month when switching to a new note. 10604 + * Find the event whose startLine..endLine range contains the given 10605 + * line number and select it in the calendar and map. 440 10606 */ 441 - resetNavigation() { 442 - this.hasUserNavigated = false; 10607 + selectEventByLine(line) { 10608 + var _a, _b, _c, _d; 10609 + const event = this.events.find( 10610 + (e) => e.startLine !== void 0 && e.endLine !== void 0 && line >= e.startLine && line <= e.endLine 10611 + ); 10612 + if (!event) { 10613 + if (this.selectedEvent) { 10614 + this.selectedEvent = null; 10615 + (_a = this.calendarController) == null ? void 0 : _a.selectEvent(null); 10616 + (_b = this.mapController) == null ? void 0 : _b.selectEvent(null); 10617 + } 10618 + return; 10619 + } 10620 + if (this.selectedEvent && this.selectedEvent.title === event.title && this.selectedEvent.date.getTime() === event.date.getTime()) { 10621 + return; 10622 + } 10623 + this.selectedEvent = event; 10624 + const evMonth = event.date.getMonth(); 10625 + const evYear = event.date.getFullYear(); 10626 + if (evYear !== this.currentMonth.getFullYear() || evMonth !== this.currentMonth.getMonth()) { 10627 + this.currentMonth = new Date(evYear, evMonth, 1); 10628 + this.renderCalendar(); 10629 + } else { 10630 + (_c = this.calendarController) == null ? void 0 : _c.selectEvent(event); 10631 + } 10632 + (_d = this.mapController) == null ? void 0 : _d.selectEvent(event); 10633 + } 10634 + /** 10635 + * Scroll the editor to the event's start line. 10636 + */ 10637 + scrollEditorToEvent(event) { 10638 + if (event.startLine === void 0) 10639 + return; 10640 + const mdView = this.app.workspace.getActiveViewOfType(import_obsidian.MarkdownView); 10641 + if (!mdView) 10642 + return; 10643 + const editor = mdView.editor; 10644 + editor.setCursor({ line: event.startLine, ch: 0 }); 10645 + editor.scrollIntoView( 10646 + { 10647 + from: { line: event.startLine, ch: 0 }, 10648 + to: { line: event.startLine, ch: 0 } 10649 + }, 10650 + true 10651 + ); 10652 + this.lastCursorLine = event.startLine; 10653 + } 10654 + /** 10655 + * Write "geo: lat,lng" sub-bullets back into the active document 10656 + * for events that were just geocoded. Inserts after each event's 10657 + * last line (endLine), processing from bottom to top so line 10658 + * numbers don't shift for earlier insertions. 10659 + */ 10660 + async writeGeoLinesToDoc(newlyGeocoded) { 10661 + const file = this.app.workspace.getActiveFile(); 10662 + if (!file) 10663 + return; 10664 + const toWrite = newlyGeocoded.filter( 10665 + (e) => e.endLine !== void 0 && e.lat !== void 0 && e.lng !== void 0 10666 + ); 10667 + if (toWrite.length === 0) 10668 + return; 10669 + toWrite.sort((a, b) => b.endLine - a.endLine); 10670 + this.isWritingGeoLines = true; 10671 + try { 10672 + await this.app.vault.process(file, (content) => { 10673 + var _a; 10674 + const lines = content.split("\n"); 10675 + for (const event of toWrite) { 10676 + const insertAfter = event.endLine; 10677 + const geoLine = ` * geo: ${event.lat},${event.lng}`; 10678 + let alreadyHasGeo = false; 10679 + for (let i = (_a = event.startLine) != null ? _a : insertAfter; i <= insertAfter; i++) { 10680 + if (/^\t[*\-]\s+geo:\s/.test(lines[i]) || /^\s{2,}[*\-]\s+geo:\s/.test(lines[i])) { 10681 + alreadyHasGeo = true; 10682 + break; 10683 + } 10684 + } 10685 + if (alreadyHasGeo) 10686 + continue; 10687 + lines.splice(insertAfter + 1, 0, geoLine); 10688 + } 10689 + return lines.join("\n"); 10690 + }); 10691 + } finally { 10692 + setTimeout(() => { 10693 + this.isWritingGeoLines = false; 10694 + }, 500); 10695 + } 443 10696 } 444 10697 }; 445 10698 446 10699 // main.ts 447 10700 var CalendarViewerPlugin = class extends import_obsidian2.Plugin { 10701 + constructor() { 10702 + super(...arguments); 10703 + this.lastActiveFilePath = null; 10704 + } 448 10705 async onload() { 449 - this.registerView(VIEW_TYPE_CALENDAR, (leaf) => new CalendarView(leaf)); 10706 + this.registerView( 10707 + VIEW_TYPE_CALENDAR, 10708 + (leaf) => new CalendarView(leaf, this) 10709 + ); 450 10710 this.addRibbonIcon("calendar", "Open Calendar View", () => { 451 10711 this.activateView(); 452 10712 }); ··· 459 10719 }); 460 10720 this.registerEvent( 461 10721 this.app.workspace.on("active-leaf-change", () => { 10722 + var _a; 10723 + const activeFile = this.app.workspace.getActiveFile(); 10724 + const activePath = (_a = activeFile == null ? void 0 : activeFile.path) != null ? _a : null; 10725 + if (activePath === this.lastActiveFilePath) 10726 + return; 10727 + this.lastActiveFilePath = activePath; 462 10728 const view = this.getCalendarView(); 463 10729 if (view) { 464 10730 view.resetNavigation(); ··· 503 10769 } 504 10770 } 505 10771 }; 10772 + /*! Bundled license information: 10773 + 10774 + leaflet/dist/leaflet-src.js: 10775 + (* @preserve 10776 + * Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com 10777 + * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade 10778 + *) 10779 + */
+15 -5
main.ts
··· 1 - import { Plugin, WorkspaceLeaf } from "obsidian"; 1 + import { Plugin } from "obsidian"; 2 2 import { CalendarView, VIEW_TYPE_CALENDAR } from "./calendarView"; 3 3 4 4 export default class CalendarViewerPlugin extends Plugin { 5 + private lastActiveFilePath: string | null = null; 6 + 5 7 async onload(): Promise<void> { 6 - this.registerView(VIEW_TYPE_CALENDAR, (leaf) => new CalendarView(leaf)); 8 + this.registerView( 9 + VIEW_TYPE_CALENDAR, 10 + (leaf) => new CalendarView(leaf, this), 11 + ); 7 12 8 13 // Ribbon icon to open the calendar sidebar 9 14 this.addRibbonIcon("calendar", "Open Calendar View", () => { ··· 19 24 }, 20 25 }); 21 26 22 - // Re-parse when the active file changes 27 + // Re-parse when the active file changes (but skip if same file re-focused) 23 28 this.registerEvent( 24 29 this.app.workspace.on("active-leaf-change", () => { 30 + const activeFile = this.app.workspace.getActiveFile(); 31 + const activePath = activeFile?.path ?? null; 32 + if (activePath === this.lastActiveFilePath) return; 33 + this.lastActiveFilePath = activePath; 34 + 25 35 const view = this.getCalendarView(); 26 36 if (view) { 27 37 view.resetNavigation(); 28 38 view.refresh(); 29 39 } 30 - }) 40 + }), 31 41 ); 32 42 33 43 // Re-parse when a file is modified (live update) ··· 40 50 view.refresh(); 41 51 } 42 52 } 43 - }) 53 + }), 44 54 ); 45 55 } 46 56
+420
mapRenderer.ts
··· 1 + import * as L from "leaflet"; 2 + import type { CalendarEvent } from "./parser"; 3 + 4 + /** 5 + * Read the Obsidian accent color from CSS custom properties. 6 + * Falls back to the default Obsidian purple if unavailable. 7 + */ 8 + function getAccentColor(): string { 9 + return getComputedStyle(document.body).getPropertyValue("--interactive-accent").trim() || "#7f6df2"; 10 + } 11 + 12 + /** 13 + * Darken a hex color by a given factor (0–1). Used for the pin stroke. 14 + */ 15 + function darkenHex(hex: string, factor: number): string { 16 + const h = hex.replace("#", ""); 17 + const r = Math.max(0, Math.round(parseInt(h.slice(0, 2), 16) * (1 - factor))); 18 + const g = Math.max(0, Math.round(parseInt(h.slice(2, 4), 16) * (1 - factor))); 19 + const b = Math.max(0, Math.round(parseInt(h.slice(4, 6), 16) * (1 - factor))); 20 + return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`; 21 + } 22 + 23 + /** 24 + * Create an SVG teardrop pin icon for map markers. 25 + * Uses the Obsidian accent color, or a muted grey for sold-out events. 26 + */ 27 + function createPinIcon(soldOut: boolean): L.DivIcon { 28 + const accent = getAccentColor(); 29 + const fill = soldOut ? "#999" : accent; 30 + const stroke = soldOut ? "#777" : darkenHex(accent, 0.25); 31 + const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="28" height="40" viewBox="0 0 28 40">` + 32 + `<path d="M14 0C6.3 0 0 6.3 0 14c0 10.5 14 26 14 26s14-15.5 14-26C28 6.3 21.7 0 14 0z" ` + 33 + `fill="${fill}" stroke="${stroke}" stroke-width="1.5"/>` + 34 + `<circle cx="14" cy="14" r="5" fill="white" opacity="0.85"/>` + 35 + `</svg>`; 36 + return L.divIcon({ 37 + html: svg, 38 + className: "cal-pin-icon", 39 + iconSize: [28, 40], 40 + iconAnchor: [14, 40], 41 + popupAnchor: [0, -36], 42 + }); 43 + } 44 + 45 + export interface MapCallbacks { 46 + onMarkerClick: (event: CalendarEvent) => void; 47 + } 48 + 49 + export interface MapController { 50 + /** Update markers to reflect current events. Pass fitBounds=true to auto-zoom. */ 51 + updateMarkers(events: CalendarEvent[], fitBounds?: boolean): void; 52 + /** Select an event: pan to its marker, open popup, highlight it. */ 53 + selectEvent(event: CalendarEvent | null): void; 54 + /** Auto-fit map bounds to all current markers. */ 55 + fitBounds(): void; 56 + /** Call when container is resized. */ 57 + invalidateSize(): void; 58 + /** Clean up the Leaflet instance. */ 59 + destroy(): void; 60 + } 61 + 62 + // Stamen Watercolor tiles hosted by the Smithsonian / Cooper Hewitt (free, no API key) 63 + const WATERCOLOR_URL = "https://watercolormaps.collection.cooperhewitt.org/tile/watercolor/{z}/{x}/{y}.jpg"; 64 + // CartoDB light labels overlay (free, no API key) 65 + const LABELS_URL = "https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png"; 66 + const TILE_ATTRIBUTION = 67 + 'Map tiles by <a href="https://stamen.com/">Stamen Design</a>, hosted by <a href="https://collection.cooperhewitt.org/">Cooper Hewitt</a>. ' + 68 + 'Labels by <a href="https://carto.com/">CARTO</a>. ' + 69 + 'Data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'; 70 + 71 + /** 72 + * Format a popup's HTML content for a list of events at the same location. 73 + */ 74 + function popupContent(events: CalendarEvent[]): string { 75 + return events 76 + .map((ev) => { 77 + const title = ev.url 78 + ? `<a href="${ev.url}" target="_blank">${escapeHtml(ev.title)}</a>` 79 + : escapeHtml(ev.title); 80 + const soldOut = ev.soldOut ? ' <span class="cal-sold-out">(Sold out)</span>' : ""; 81 + const date = ev.date.toLocaleDateString("en-US", { 82 + weekday: "short", 83 + month: "short", 84 + day: "numeric", 85 + }); 86 + const time = ev.rawTime ? `, ${escapeHtml(ev.rawTime)}` : ""; 87 + const venue = ev.venue ? `<br><span class="cal-popup-venue">${escapeHtml(ev.venue)}</span>` : ""; 88 + return `<div class="cal-popup-event"><strong>${title}</strong>${soldOut}<br>${date}${time}${venue}</div>`; 89 + }) 90 + .join(""); 91 + } 92 + 93 + function escapeHtml(s: string): string { 94 + return s 95 + .replace(/&/g, "&amp;") 96 + .replace(/</g, "&lt;") 97 + .replace(/>/g, "&gt;") 98 + .replace(/"/g, "&quot;"); 99 + } 100 + 101 + /** 102 + * Group events by their lat/lng coordinates (rounded to avoid floating point issues). 103 + */ 104 + function groupByLocation(events: CalendarEvent[]): Map<string, CalendarEvent[]> { 105 + const groups = new Map<string, CalendarEvent[]>(); 106 + for (const ev of events) { 107 + if (ev.lat === undefined || ev.lng === undefined) continue; 108 + // Round to 6 decimal places for grouping 109 + const key = `${ev.lat.toFixed(6)},${ev.lng.toFixed(6)}`; 110 + if (!groups.has(key)) groups.set(key, []); 111 + groups.get(key)!.push(ev); 112 + } 113 + return groups; 114 + } 115 + 116 + /** 117 + * Create a Leaflet map in the given container and return a MapController. 118 + */ 119 + export function createMap(container: HTMLElement, events: CalendarEvent[], callbacks: MapCallbacks): MapController { 120 + 121 + // Inject Leaflet CSS if not already present 122 + if (!document.getElementById("leaflet-css")) { 123 + const link = document.createElement("style"); 124 + link.id = "leaflet-css"; 125 + link.textContent = leafletCss(); 126 + document.head.appendChild(link); 127 + } 128 + 129 + const mapDiv = container.createDiv({ cls: "cal-map" }); 130 + 131 + const map = L.map(mapDiv, { 132 + zoomControl: true, 133 + attributionControl: true, 134 + }); 135 + 136 + // Watercolor base layer 137 + L.tileLayer(WATERCOLOR_URL, { 138 + attribution: TILE_ATTRIBUTION, 139 + maxZoom: 19, 140 + }).addTo(map); 141 + 142 + // Labels overlay on top of watercolor 143 + L.tileLayer(LABELS_URL, { 144 + maxZoom: 19, 145 + subdomains: "abcd", 146 + pane: "overlayPane", 147 + }).addTo(map); 148 + 149 + // Track markers and their associated events 150 + const markerLayer = L.layerGroup().addTo(map); 151 + let markerMap = new Map<string, { marker: L.Marker; events: CalendarEvent[] }>(); 152 + let selectedMarker: L.Marker | null = null; 153 + let highlightCircle: L.CircleMarker | null = null; 154 + 155 + function clearHighlight(): void { 156 + if (highlightCircle) { 157 + highlightCircle.remove(); 158 + highlightCircle = null; 159 + } 160 + selectedMarker = null; 161 + } 162 + 163 + function highlightMarker(marker: L.Marker): void { 164 + clearHighlight(); 165 + selectedMarker = marker; 166 + const latlng = marker.getLatLng(); 167 + highlightCircle = L.circleMarker(latlng, { 168 + radius: 18, 169 + color: "var(--interactive-accent, #7b6cd9)", 170 + fillColor: "var(--interactive-accent, #7b6cd9)", 171 + fillOpacity: 0.2, 172 + weight: 2, 173 + className: "cal-marker-highlight", 174 + }).addTo(map); 175 + } 176 + 177 + function buildMarkers(evts: CalendarEvent[]): void { 178 + markerLayer.clearLayers(); 179 + markerMap.clear(); 180 + clearHighlight(); 181 + 182 + const groups = groupByLocation(evts); 183 + 184 + for (const [key, groupEvents] of groups) { 185 + const [lat, lng] = key.split(",").map(Number); 186 + 187 + const allSoldOut = groupEvents.every((e) => e.soldOut); 188 + 189 + const marker = L.marker([lat, lng], { 190 + icon: createPinIcon(allSoldOut), 191 + }); 192 + 193 + marker.bindPopup(popupContent(groupEvents), { 194 + maxWidth: 240, 195 + className: "cal-map-popup", 196 + }); 197 + 198 + marker.on("click", () => { 199 + highlightMarker(marker); 200 + // Fire callback for the first event in the group 201 + callbacks.onMarkerClick(groupEvents[0]); 202 + }); 203 + 204 + marker.addTo(markerLayer); 205 + markerMap.set(key, { marker, events: groupEvents }); 206 + } 207 + } 208 + 209 + buildMarkers(events); 210 + 211 + function doFitBounds(): void { 212 + const allMarkers = Array.from(markerMap.values()).map((m) => m.marker); 213 + if (allMarkers.length === 0) { 214 + // Default to a world view 215 + map.setView([20, 0], 2); 216 + return; 217 + } 218 + if (allMarkers.length === 1) { 219 + map.setView(allMarkers[0].getLatLng(), 13); 220 + return; 221 + } 222 + const group = L.featureGroup(allMarkers); 223 + map.fitBounds(group.getBounds().pad(0.15)); 224 + } 225 + 226 + // Initial fit after a tick (let the container settle) 227 + setTimeout(() => { 228 + map.invalidateSize(); 229 + doFitBounds(); 230 + }, 50); 231 + 232 + const controller: MapController = { 233 + updateMarkers(evts: CalendarEvent[], fit = true): void { 234 + buildMarkers(evts); 235 + if (fit) { 236 + doFitBounds(); 237 + } 238 + }, 239 + 240 + selectEvent(event: CalendarEvent | null): void { 241 + if (!event) { 242 + clearHighlight(); 243 + map.closePopup(); 244 + return; 245 + } 246 + if (event.lat === undefined || event.lng === undefined) return; 247 + 248 + const key = `${event.lat.toFixed(6)},${event.lng.toFixed(6)}`; 249 + const entry = markerMap.get(key); 250 + if (entry) { 251 + highlightMarker(entry.marker); 252 + entry.marker.openPopup(); 253 + map.panTo(entry.marker.getLatLng(), { animate: true }); 254 + } 255 + }, 256 + 257 + fitBounds(): void { 258 + doFitBounds(); 259 + }, 260 + 261 + invalidateSize(): void { 262 + map.invalidateSize(); 263 + }, 264 + 265 + destroy(): void { 266 + map.remove(); 267 + }, 268 + }; 269 + 270 + return controller; 271 + } 272 + 273 + /** 274 + * Inline Leaflet CSS — we inject this rather than importing a CSS file 275 + * so esbuild doesn't need a CSS loader and the plugin stays self-contained. 276 + * 277 + * This is the minified core Leaflet 1.9.4 CSS. 278 + */ 279 + function leafletCss(): string { 280 + // We'll load from the installed leaflet package at runtime via a simpler approach: 281 + // Import the CSS content as a string. Since esbuild can't handle CSS imports 282 + // from node_modules without a loader, we inline the essential Leaflet styles. 283 + return ` 284 + /* Leaflet 1.9.4 — essential styles */ 285 + .leaflet-pane, .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow, 286 + .leaflet-tile-container, .leaflet-pane > svg, .leaflet-pane > canvas, 287 + .leaflet-zoom-box, .leaflet-image-layer, .leaflet-layer { 288 + position: absolute; left: 0; top: 0; 289 + } 290 + .leaflet-container { overflow: hidden; } 291 + .leaflet-tile, .leaflet-marker-icon, .leaflet-marker-shadow { user-select: none; -webkit-user-select: none; } 292 + .leaflet-tile::selection { background: transparent; } 293 + .leaflet-safari .leaflet-tile { image-rendering: -webkit-optimize-contrast; } 294 + .leaflet-safari .leaflet-tile-container { width: 1600px; height: 1600px; -webkit-transform-origin: 0 0; } 295 + .leaflet-marker-icon, .leaflet-marker-shadow { display: block; } 296 + .leaflet-container .leaflet-overlay-pane svg { max-width: none !important; max-height: none !important; } 297 + .leaflet-container .leaflet-marker-pane img, 298 + .leaflet-container .leaflet-shadow-pane img, 299 + .leaflet-container .leaflet-tile-pane img, 300 + .leaflet-container img.leaflet-image-layer, 301 + .leaflet-container .leaflet-tile { max-width: none !important; max-height: none !important; width: auto; padding: 0; } 302 + .leaflet-container.leaflet-touch-zoom { touch-action: pan-x pan-y; } 303 + .leaflet-container.leaflet-touch-drag { touch-action: none; touch-action: pinch-zoom; } 304 + .leaflet-container.leaflet-touch-drag.leaflet-touch-zoom { touch-action: none; } 305 + .leaflet-container { -webkit-tap-highlight-color: transparent; } 306 + .leaflet-container a { -webkit-tap-highlight-color: rgba(51,181,229,0.4); } 307 + .leaflet-tile { filter: inherit; visibility: hidden; } 308 + .leaflet-tile-loaded { visibility: inherit; } 309 + .leaflet-zoom-box { width: 0; height: 0; box-sizing: border-box; z-index: 800; } 310 + .leaflet-overlay-pane svg { -moz-user-select: none; } 311 + .leaflet-pane { z-index: 400; } 312 + .leaflet-tile-pane { z-index: 200; } 313 + .leaflet-overlay-pane { z-index: 400; } 314 + .leaflet-shadow-pane { z-index: 500; } 315 + .leaflet-marker-pane { z-index: 600; } 316 + .leaflet-tooltip-pane { z-index: 650; } 317 + .leaflet-popup-pane { z-index: 700; } 318 + .leaflet-map-pane canvas { z-index: 100; } 319 + .leaflet-map-pane svg { z-index: 200; } 320 + .leaflet-vml-shape { width: 1px; height: 1px; } 321 + .lvml { behavior: url(#default#VML); display: inline-block; position: absolute; } 322 + .leaflet-control { position: relative; z-index: 800; pointer-events: visiblePainted; pointer-events: auto; } 323 + .leaflet-top, .leaflet-bottom { position: absolute; z-index: 1000; pointer-events: none; } 324 + .leaflet-top { top: 0; } 325 + .leaflet-right { right: 0; } 326 + .leaflet-bottom { bottom: 0; } 327 + .leaflet-left { left: 0; } 328 + .leaflet-control { float: left; clear: both; } 329 + .leaflet-right .leaflet-control { float: right; } 330 + .leaflet-top .leaflet-control { margin-top: 10px; } 331 + .leaflet-bottom .leaflet-control { margin-bottom: 10px; } 332 + .leaflet-left .leaflet-control { margin-left: 10px; } 333 + .leaflet-right .leaflet-control { margin-right: 10px; } 334 + .leaflet-fade-anim .leaflet-popup { opacity: 1; transition: opacity 0.2s linear; } 335 + .leaflet-fade-anim .leaflet-map-pane .leaflet-popup { opacity: 1; } 336 + .leaflet-zoom-animated { transform-origin: 0 0; } 337 + .leaflet-zoom-anim .leaflet-zoom-animated { will-change: transform; transition: transform 0.25s cubic-bezier(0,0,0.25,1); } 338 + .leaflet-zoom-anim .leaflet-tile, .leaflet-pan-anim .leaflet-tile { transition: none; } 339 + .leaflet-zoom-anim .leaflet-zoom-hide { visibility: hidden; } 340 + .leaflet-interactive { cursor: pointer; } 341 + .leaflet-grab { cursor: grab; } 342 + .leaflet-crosshair, .leaflet-crosshair .leaflet-interactive { cursor: crosshair; } 343 + .leaflet-popup-pane, .leaflet-control { cursor: auto; } 344 + .leaflet-dragging .leaflet-grab, .leaflet-dragging .leaflet-grab .leaflet-interactive, 345 + .leaflet-dragging .leaflet-marker-draggable { cursor: move; cursor: grabbing; } 346 + .leaflet-marker-icon, .leaflet-marker-shadow, .leaflet-image-layer, 347 + .leaflet-pane > svg path, .leaflet-tile-container { pointer-events: none; } 348 + .leaflet-marker-icon.leaflet-interactive, .leaflet-image-layer.leaflet-interactive, 349 + .leaflet-pane > svg path.leaflet-interactive, svg.leaflet-image-layer.leaflet-interactive path { pointer-events: visiblePainted; pointer-events: auto; } 350 + .leaflet-container a.leaflet-active { outline: 2px solid orange; } 351 + .leaflet-zoom-box { border: 2px dotted #38f; background: rgba(255,255,255,0.5); } 352 + .leaflet-bar { box-shadow: 0 1px 5px rgba(0,0,0,0.65); border-radius: 4px; } 353 + .leaflet-bar a, .leaflet-bar a:hover { background-color: #fff; border-bottom: 1px solid #ccc; width: 26px; height: 26px; line-height: 26px; display: block; text-align: center; text-decoration: none; color: black; } 354 + .leaflet-bar a, .leaflet-control-layers-toggle { background-position: 50% 50%; background-repeat: no-repeat; display: block; } 355 + .leaflet-bar a:hover, .leaflet-bar a:focus { background-color: #f4f4f4; } 356 + .leaflet-bar a:first-child { border-top-left-radius: 4px; border-top-right-radius: 4px; } 357 + .leaflet-bar a:last-child { border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; border-bottom: none; } 358 + .leaflet-bar a.leaflet-disabled { cursor: default; background-color: #f4f4f4; color: #bbb; } 359 + .leaflet-touch .leaflet-bar a { width: 30px; height: 30px; line-height: 30px; } 360 + .leaflet-touch .leaflet-bar a:first-child { border-top-left-radius: 2px; border-top-right-radius: 2px; } 361 + .leaflet-touch .leaflet-bar a:last-child { border-bottom-left-radius: 2px; border-bottom-right-radius: 2px; } 362 + .leaflet-control-zoom-in, .leaflet-control-zoom-out { font: bold 18px 'Lucida Console', Monaco, monospace; text-indent: 1px; } 363 + .leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out { font-size: 22px; } 364 + .leaflet-control-layers { box-shadow: 0 1px 5px rgba(0,0,0,0.4); background: #fff; border-radius: 5px; } 365 + .leaflet-control-layers-toggle { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAQAAAADQ4RFAAACf0lEQVR4AY1TA5AVURDsmc22bdu2bdu2bdu2bdu2bZ3v3P3f1t2q7mL6AQBSNG03EMHQF8MwEiNxBmfxMz7FJ/iROI0zOINfCF2RC9lRG8MxFRdwSw0EcBu3cBEXcRt31ECq4AFMI3RFLVRHZ3RBf0zAHJzBDdzEHdzFfTzAQzzCYzzBUzzDc7zASwjBV3iDb/AO3+MHkiSf8dPf4mt8ha/xJb7AF/gcn+FTfIJP8DE+wof4AO/jPbyLd/A23sKbeAOv43W8hlfxCl7Gy3gJL+IFPIdncQ+P8RRP8ARP8BSvwGOAkBm50AsrsR+38BCv4BLO4DTu4h6e4SVe4CXe4CU+wPt4D+/iHbyNt/Am3sDreA2v4hW8jJfwIl7Ac3gWz+AuHuMpnuAJnuI5eMQjP4cLauA+buE6LuMCzuE0TuE8ruEObuMhHuEWHuABnuExnuIRnuAJnuEZ+FfFa3gVr+BlvISX8CJewPN4Ds/iGdzFYzzFEzzBUzzHSwjBV/gG3+I7fI8fCKLkM37+W3yNr/A1vsSX+AKf4zN8ik/wMT7Ch/gA7+M9vIt38DbewsN4C2/iDbyO1/EaXsUreBkv4SW8iBfwPJ7DU7iHx3iKx3iCJ3iGF+CRH+Ey7uEOLuMMzuMiruEGbuI2buMO7uE+HuIhHuMpHuMJnuI5XoJHvIJX8Rpexat4Ba/gZbyEl/ACnsdzeBbP4C4e4yme4Ame4hlewZ8+DJmRC30xAbtxDQ/xGu7gNE7hMu7gLu7jAR7iMZ7gKR7jCZ7hOV6AAG/hTbyBt/AWHsJbeAsP4x28i3fwNt7CG3gdr+E1vIpX8DJewot4Ac/hWTyNu3iEx3j67w8P/z9FWIJUIAAAAABJRU5ErkJggg==); width: 36px; height: 36px; } 366 + .leaflet-retina .leaflet-control-layers-toggle { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAA0CAQAAABvcdNgAAAEsklEQVR4AWL4TydIhpZK1kAAAABJRU5ErkJggg==); background-size: 26px 26px; } 367 + .leaflet-touch .leaflet-control-layers-toggle { width: 44px; height: 44px; } 368 + .leaflet-control-layers .leaflet-control-layers-list, .leaflet-control-layers-expanded .leaflet-control-layers-toggle { display: none; } 369 + .leaflet-control-layers-expanded .leaflet-control-layers-list { display: block; position: relative; } 370 + .leaflet-control-layers-expanded { padding: 6px 10px 6px 6px; color: #333; background: #fff; } 371 + .leaflet-control-layers-scrollbar { overflow-y: scroll; overflow-x: hidden; padding-right: 5px; } 372 + .leaflet-control-layers-selector { margin-top: 2px; position: relative; top: 1px; } 373 + .leaflet-control-layers label { display: block; font-size: 13px; font-size: 1.08333em; } 374 + .leaflet-control-layers-separator { height: 0; border-top: 1px solid #ddd; margin: 5px -10px 5px -6px; } 375 + .leaflet-default-icon-path { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAFgUlEQVR4Aa1XA5BjWRTN2oW17d3YassW17LFNesR17LWtAnOez/J5tsupervised/cLOGeli2lGZjUtMPail370a57y6u6deuj75NAfO/6fOM0M0SUR4JYAAAAABJRU5ErkJggg==); } 376 + .leaflet-container .leaflet-control-attribution { background: #fff; background: rgba(255,255,255,0.8); margin: 0; } 377 + .leaflet-control-attribution, .leaflet-control-scale-line { padding: 0 5px; color: #333; line-height: 1.4; } 378 + .leaflet-control-attribution a { text-decoration: none; } 379 + .leaflet-control-attribution a:hover, .leaflet-control-attribution a:focus { text-decoration: underline; } 380 + .leaflet-attribution-flag { display: none !important; } 381 + .leaflet-left .leaflet-control-scale { margin-left: 5px; } 382 + .leaflet-bottom .leaflet-control-scale { margin-bottom: 5px; } 383 + .leaflet-control-scale-line { border: 2px solid #777; border-top: none; line-height: 1.1; padding: 2px 5px 1px; white-space: nowrap; overflow: hidden; box-sizing: border-box; background: rgba(255,255,255,0.5); } 384 + .leaflet-control-scale-line:not(:first-child) { border-top: 2px solid #777; border-bottom: none; margin-top: -2px; } 385 + .leaflet-control-scale-line:not(:first-child):not(:last-child) { border-bottom: 2px solid #777; } 386 + .leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers, 387 + .leaflet-touch .leaflet-bar { box-shadow: none; } 388 + .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { border: 2px solid rgba(0,0,0,0.2); background-clip: padding-box; } 389 + .leaflet-popup { position: absolute; text-align: center; margin-bottom: 20px; } 390 + .leaflet-popup-content-wrapper { padding: 1px; text-align: left; border-radius: 12px; } 391 + .leaflet-popup-content { margin: 13px 24px 13px 20px; line-height: 1.3; font-size: 13px; font-size: 1.08333em; min-height: 1px; } 392 + .leaflet-popup-content p { margin: 17px 0; margin: 1.3em 0; } 393 + .leaflet-popup-tip-container { width: 40px; height: 20px; position: absolute; left: 50%; margin-top: -1px; margin-left: -20px; overflow: hidden; pointer-events: none; } 394 + .leaflet-popup-tip { width: 17px; height: 17px; padding: 1px; margin: -10px auto 0; pointer-events: auto; transform: rotate(45deg); } 395 + .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: white; color: #333; box-shadow: 0 3px 14px rgba(0,0,0,0.4); } 396 + .leaflet-container a.leaflet-popup-close-button { position: absolute; top: 0; right: 0; border: none; text-align: center; width: 24px; height: 24px; font: 16px/24px Tahoma, Verdana, sans-serif; color: #757575; text-decoration: none; background: transparent; } 397 + .leaflet-container a.leaflet-popup-close-button:hover, .leaflet-container a.leaflet-popup-close-button:focus { color: #585858; } 398 + .leaflet-popup-scrolled { overflow: auto; } 399 + .leaflet-oldie .leaflet-popup-content-wrapper { -ms-zoom: 1; } 400 + .leaflet-oldie .leaflet-popup-tip { width: 24px; margin: 0 auto; -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); } 401 + .leaflet-oldie .leaflet-control-zoom, .leaflet-oldie .leaflet-control-layers, 402 + .leaflet-oldie .leaflet-popup-content-wrapper, .leaflet-oldie .leaflet-popup-tip { border: 1px solid #999; } 403 + .leaflet-div-icon { background: #fff; border: 1px solid #666; } 404 + .leaflet-tooltip { position: absolute; padding: 6px; background-color: #fff; border: 1px solid #fff; border-radius: 3px; color: #222; white-space: nowrap; user-select: none; pointer-events: none; box-shadow: 0 1px 3px rgba(0,0,0,0.4); } 405 + .leaflet-tooltip.leaflet-interactive { cursor: pointer; pointer-events: auto; } 406 + .leaflet-tooltip-top:before, .leaflet-tooltip-bottom:before, 407 + .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { position: absolute; pointer-events: none; border: 6px solid transparent; background: transparent; content: ""; } 408 + .leaflet-tooltip-bottom { margin-top: 6px; } 409 + .leaflet-tooltip-top { margin-top: -6px; } 410 + .leaflet-tooltip-bottom:before, .leaflet-tooltip-top:before { left: 50%; margin-left: -6px; } 411 + .leaflet-tooltip-top:before { bottom: 0; margin-bottom: -12px; border-top-color: #fff; } 412 + .leaflet-tooltip-bottom:before { top: 0; margin-top: -12px; margin-left: -6px; border-bottom-color: #fff; } 413 + .leaflet-tooltip-left { margin-left: -6px; } 414 + .leaflet-tooltip-right { margin-left: 6px; } 415 + .leaflet-tooltip-left:before, .leaflet-tooltip-right:before { top: 50%; margin-top: -6px; } 416 + .leaflet-tooltip-left:before { right: 0; margin-right: -12px; border-left-color: #fff; } 417 + .leaflet-tooltip-right:before { left: 0; margin-left: -12px; border-right-color: #fff; } 418 + @media print { .leaflet-control { -webkit-print-color-adjust: exact; print-color-adjust: exact; } } 419 + `; 420 + }
+25
package-lock.json
··· 8 8 "name": "obs-calendar-viewer", 9 9 "version": "1.0.0", 10 10 "license": "MIT", 11 + "dependencies": { 12 + "@types/leaflet": "^1.9.21", 13 + "leaflet": "^1.9.4" 14 + }, 11 15 "devDependencies": { 12 16 "@types/node": "^20.11.0", 13 17 "builtin-modules": "^3.3.0", ··· 458 462 "dev": true, 459 463 "license": "MIT" 460 464 }, 465 + "node_modules/@types/geojson": { 466 + "version": "7946.0.16", 467 + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", 468 + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", 469 + "license": "MIT" 470 + }, 471 + "node_modules/@types/leaflet": { 472 + "version": "1.9.21", 473 + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.21.tgz", 474 + "integrity": "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w==", 475 + "license": "MIT", 476 + "dependencies": { 477 + "@types/geojson": "*" 478 + } 479 + }, 461 480 "node_modules/@types/node": { 462 481 "version": "20.19.33", 463 482 "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", ··· 537 556 "@esbuild/win32-ia32": "0.20.2", 538 557 "@esbuild/win32-x64": "0.20.2" 539 558 } 559 + }, 560 + "node_modules/leaflet": { 561 + "version": "1.9.4", 562 + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", 563 + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", 564 + "license": "BSD-2-Clause" 540 565 }, 541 566 "node_modules/moment": { 542 567 "version": "2.29.4",
+6 -2
package.json
··· 15 15 "builtin-modules": "^3.3.0", 16 16 "esbuild": "^0.20.0", 17 17 "obsidian": "latest", 18 - "typescript": "^5.3.0", 19 - "tslib": "^2.6.0" 18 + "tslib": "^2.6.0", 19 + "typescript": "^5.3.0" 20 + }, 21 + "dependencies": { 22 + "@types/leaflet": "^1.9.21", 23 + "leaflet": "^1.9.4" 20 24 } 21 25 }
+48 -12
parser.ts
··· 9 9 soldOut: boolean; 10 10 rawTime?: string; 11 11 notes?: string; 12 + lat?: number; 13 + lng?: number; 14 + /** 0-based line number of the first line of this event's block. */ 15 + startLine?: number; 16 + /** 0-based line number of the last line of this event's block. */ 17 + endLine?: number; 12 18 } 13 19 14 20 const MONTHS: Record<string, number> = { ··· 17 23 }; 18 24 19 25 const URL_RE = /^https?:\/\/\S+$/; 26 + 27 + // "geo: 47.6062,-122.3321" — inline geocode result 28 + const GEO_RE = /^geo:\s*(-?\d+\.?\d*),\s*(-?\d+\.?\d*)$/; 20 29 21 30 // "Monday 27 April 2026" — Songkick style 22 31 const DATE_WEEKDAY_DD_MONTH_YYYY = ··· 133 142 const lines = markdown.split("\n"); 134 143 const events: CalendarEvent[] = []; 135 144 136 - // Group lines into blocks: each top-level bullet starts a block 137 - const blocks: string[][] = []; 138 - let currentBlock: string[] | null = null; 145 + // Group lines into blocks: each top-level bullet starts a block. 146 + // Track the 0-based start and end line numbers of each block. 147 + interface Block { 148 + lines: string[]; 149 + startLine: number; 150 + endLine: number; 151 + } 152 + const blocks: Block[] = []; 153 + let currentBlock: Block | null = null; 139 154 140 - for (const line of lines) { 155 + for (let i = 0; i < lines.length; i++) { 156 + const line = lines[i]; 141 157 // Top-level bullet: starts with `* ` (possibly after stripping leading whitespace at level 0) 142 158 if (/^[*\-]\s/.test(line)) { 143 - currentBlock = [line.replace(/^[*\-]\s+/, "").trim()]; 159 + currentBlock = { 160 + lines: [line.replace(/^[*\-]\s+/, "").trim()], 161 + startLine: i, 162 + endLine: i, 163 + }; 144 164 blocks.push(currentBlock); 145 165 } else if (currentBlock && /^\t[*\-]\s/.test(line)) { 146 166 // Sub-bullet (tab-indented) 147 - currentBlock.push(line.replace(/^\t[*\-]\s+/, "").trim()); 167 + currentBlock.lines.push(line.replace(/^\t[*\-]\s+/, "").trim()); 168 + currentBlock.endLine = i; 148 169 } else if (currentBlock && /^\s{2,}[*\-]\s/.test(line)) { 149 170 // Sub-bullet (space-indented) 150 - currentBlock.push(line.replace(/^\s+[*\-]\s+/, "").trim()); 171 + currentBlock.lines.push(line.replace(/^\s+[*\-]\s+/, "").trim()); 172 + currentBlock.endLine = i; 151 173 } 152 174 } 153 175 154 176 for (const block of blocks) { 155 - if (block.length === 0) continue; 177 + if (block.lines.length === 0) continue; 156 178 157 - const firstLine = block[0]; 179 + const firstLine = block.lines[0]; 158 180 if (!firstLine) continue; 159 181 160 - const event: Partial<CalendarEvent> = { soldOut: false }; 182 + const event: Partial<CalendarEvent> = { 183 + soldOut: false, 184 + startLine: block.startLine, 185 + endLine: block.endLine, 186 + }; 161 187 162 188 // First line: URL or plain title 163 189 if (URL_RE.test(firstLine)) { ··· 169 195 } 170 196 171 197 // Process sub-bullets 172 - const subs = block.slice(1); 198 + const subs = block.lines.slice(1); 173 199 let dateFound = false; 174 200 175 201 for (const sub of subs) { 176 202 if (!sub) continue; 177 203 204 + // Try to parse as inline geocode result: "geo: lat,lng" 205 + const geoMatch = sub.match(GEO_RE); 206 + if (geoMatch) { 207 + event.lat = parseFloat(geoMatch[1]); 208 + event.lng = parseFloat(geoMatch[2]); 209 + continue; 210 + } 211 + 178 212 // Try to parse as date first 179 213 if (!dateFound) { 180 214 const parsed = tryParseDate(sub); ··· 196 230 197 231 // Try to parse as venue/location (contains markdown links) 198 232 if (/\[.*\]\(.*\)/.test(sub)) { 199 - const links = extractMarkdownLinks(sub); 233 + const allParts = extractMarkdownLinks(sub); 234 + // Filter to only actual markdown links (skip plain-text separators like commas) 235 + const links = allParts.filter((p) => p.url); 200 236 if (links.length >= 1) { 201 237 event.venue = links[0].text; 202 238 event.venueUrl = links[0].url;
+80 -14
renderer.ts
··· 9 9 export interface CalendarCallbacks { 10 10 onPrevMonth: () => void; 11 11 onNextMonth: () => void; 12 + onEventClick: (event: CalendarEvent) => void; 13 + } 14 + 15 + export interface CalendarController { 16 + /** Highlight an event chip in the calendar. Navigates to its month if needed. Returns true if month changed. */ 17 + selectEvent(event: CalendarEvent | null): boolean; 12 18 } 13 19 14 20 /** ··· 90 96 } 91 97 92 98 function positionPopover(popover: HTMLElement, anchor: HTMLElement, container: HTMLElement): void { 93 - // We'll position after it's in the DOM so we can measure 94 99 requestAnimationFrame(() => { 95 100 const anchorRect = anchor.getBoundingClientRect(); 96 101 const containerRect = container.getBoundingClientRect(); ··· 99 104 let top = anchorRect.bottom - containerRect.top + 4; 100 105 let left = anchorRect.left - containerRect.left; 101 106 102 - // Keep popover within container bounds 103 107 if (left + popoverRect.width > containerRect.width) { 104 108 left = containerRect.width - popoverRect.width - 8; 105 109 } 106 110 if (left < 0) left = 4; 107 111 108 - // If popover would go below container, show it above 109 112 if (top + popoverRect.height > containerRect.height) { 110 113 top = anchorRect.top - containerRect.top - popoverRect.height - 4; 111 114 } ··· 115 118 }); 116 119 } 117 120 121 + /** 122 + * Render a calendar month grid into the container. 123 + * Returns a CalendarController for programmatic interaction. 124 + */ 118 125 export function renderCalendar( 119 126 container: HTMLElement, 120 127 currentMonth: Date, 121 128 events: CalendarEvent[], 122 129 callbacks: CalendarCallbacks, 123 - ): void { 130 + selectedEvent?: CalendarEvent | null, 131 + ): CalendarController { 124 132 container.empty(); 125 133 container.addClass("cal-container"); 126 134 ··· 145 153 const grid = container.createDiv({ cls: "cal-grid" }); 146 154 const firstOfMonth = new Date(year, month, 1); 147 155 const daysInMonth = new Date(year, month + 1, 0).getDate(); 148 - const startOffset = mondayIndex(firstOfMonth); // how many blank cells before day 1 156 + const startOffset = mondayIndex(firstOfMonth); 149 157 150 158 const dayEvents = eventsByDay(events, year, month); 151 159 152 - // Track active popover so we can remove it 160 + // Track active popover 153 161 let activePopover: HTMLElement | null = null; 154 - 155 162 const removePopover = () => { 156 163 if (activePopover) { 157 164 activePopover.remove(); ··· 159 166 } 160 167 }; 161 168 169 + // Track chips by event title+date for programmatic selection 170 + const chipMap = new Map<string, HTMLElement>(); 171 + 172 + function eventKey(ev: CalendarEvent): string { 173 + return `${ev.title}::${ev.date.getTime()}`; 174 + } 175 + 162 176 // Leading blank cells 163 177 for (let i = 0; i < startOffset; i++) { 164 178 grid.createDiv({ cls: "cal-cell cal-cell-empty" }); ··· 175 189 const eventsContainer = cell.createDiv({ cls: "cal-cell-events" }); 176 190 177 191 for (const ev of eventsForDay) { 178 - const chip = eventsContainer.createDiv({ 179 - cls: `cal-event-chip${ev.soldOut ? " cal-event-sold-out" : ""}`, 180 - text: ev.title, 192 + const isSelected = selectedEvent && eventKey(ev) === eventKey(selectedEvent); 193 + const cls = [ 194 + "cal-event-chip", 195 + ev.soldOut ? "cal-event-sold-out" : "", 196 + isSelected ? "cal-event-selected" : "", 197 + ].filter(Boolean).join(" "); 198 + 199 + const chip = eventsContainer.createDiv({ cls, text: ev.title }); 200 + chipMap.set(eventKey(ev), chip); 201 + 202 + // Click to select and notify 203 + chip.addEventListener("click", (e: MouseEvent) => { 204 + e.stopPropagation(); 205 + // Remove previous selection 206 + container.querySelectorAll(".cal-event-selected").forEach( 207 + (el) => el.classList.remove("cal-event-selected") 208 + ); 209 + chip.classList.add("cal-event-selected"); 210 + callbacks.onEventClick(ev); 181 211 }); 182 212 183 213 chip.addEventListener("mouseenter", () => { ··· 186 216 }); 187 217 188 218 chip.addEventListener("mouseleave", (e: MouseEvent) => { 189 - // Small delay to allow moving to popover 190 219 setTimeout(() => { 191 220 if (activePopover && !activePopover.contains(e.relatedTarget as Node)) { 192 221 removePopover(); ··· 196 225 } 197 226 } 198 227 199 - // Check if this is today 228 + // Today highlight 200 229 const now = new Date(); 201 230 if (year === now.getFullYear() && month === now.getMonth() && day === now.getDate()) { 202 231 cell.addClass("cal-cell-today"); 203 232 } 204 233 } 205 234 206 - // Trailing blank cells to fill the last row 235 + // Trailing blank cells 207 236 const totalCells = startOffset + daysInMonth; 208 237 const trailingCells = totalCells % 7 === 0 ? 0 : 7 - (totalCells % 7); 209 238 for (let i = 0; i < trailingCells; i++) { 210 239 grid.createDiv({ cls: "cal-cell cal-cell-empty" }); 211 240 } 212 241 213 - // Dismiss popover when clicking on the container background 242 + // Dismiss popover on background click 214 243 container.addEventListener("click", (e: MouseEvent) => { 215 244 if (activePopover && !activePopover.contains(e.target as Node)) { 216 245 removePopover(); 217 246 } 218 247 }); 248 + 249 + // Scroll selected chip into view if present 250 + if (selectedEvent) { 251 + const selectedChip = chipMap.get(eventKey(selectedEvent)); 252 + if (selectedChip) { 253 + requestAnimationFrame(() => selectedChip.scrollIntoView({ block: "nearest" })); 254 + } 255 + } 256 + 257 + // Return controller 258 + const controller: CalendarController = { 259 + selectEvent(event: CalendarEvent | null): boolean { 260 + // Clear previous selection 261 + container.querySelectorAll(".cal-event-selected").forEach( 262 + (el) => el.classList.remove("cal-event-selected") 263 + ); 264 + 265 + if (!event) return false; 266 + 267 + // Check if event is in the current month 268 + const evYear = event.date.getFullYear(); 269 + const evMonth = event.date.getMonth(); 270 + if (evYear !== year || evMonth !== month) { 271 + // Need to navigate — caller should re-render with the new month 272 + return true; 273 + } 274 + 275 + const chip = chipMap.get(eventKey(event)); 276 + if (chip) { 277 + chip.classList.add("cal-event-selected"); 278 + chip.scrollIntoView({ block: "nearest" }); 279 + } 280 + return false; 281 + }, 282 + }; 283 + 284 + return controller; 219 285 }
+123 -2
styles.css
··· 1 1 /* Calendar Viewer - styles.css */ 2 2 3 + /* ── Split pane layout ── */ 4 + 5 + .cal-view-root { 6 + display: flex; 7 + flex-direction: column; 8 + height: 100%; 9 + overflow: hidden; 10 + } 11 + 12 + .cal-pane-top { 13 + flex: 1 1 50%; 14 + min-height: 0; 15 + overflow-y: auto; 16 + position: relative; 17 + border-bottom: 1px solid var(--background-modifier-border); 18 + } 19 + 20 + .cal-pane-bottom { 21 + flex: 1 1 50%; 22 + min-height: 0; 23 + position: relative; 24 + } 25 + 26 + /* ── Calendar container ── */ 27 + 3 28 .cal-container { 4 29 padding: 8px; 5 30 font-family: var(--font-interface); 6 31 position: relative; 7 - overflow-y: auto; 8 - height: 100%; 9 32 } 10 33 11 34 /* ── Header ── */ ··· 131 154 opacity: 0.6; 132 155 } 133 156 157 + .cal-event-selected { 158 + outline: 2px solid var(--text-accent); 159 + outline-offset: 1px; 160 + opacity: 1 !important; 161 + } 162 + 134 163 /* ── Popover ── */ 135 164 136 165 .cal-popover { ··· 192 221 font-style: italic; 193 222 margin-top: 4px; 194 223 } 224 + 225 + /* ── Map ── */ 226 + 227 + .cal-map { 228 + width: 100%; 229 + height: 100%; 230 + } 231 + 232 + /* SVG teardrop pin markers */ 233 + .cal-pin-icon { 234 + background: none !important; 235 + border: none !important; 236 + filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35)); 237 + } 238 + 239 + /* Override Leaflet popup styles to match Obsidian theme */ 240 + .cal-map-popup .leaflet-popup-content-wrapper { 241 + background: var(--background-primary); 242 + color: var(--text-normal); 243 + border-radius: 6px; 244 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); 245 + font-family: var(--font-interface); 246 + font-size: 12px; 247 + } 248 + 249 + .cal-map-popup .leaflet-popup-tip { 250 + background: var(--background-primary); 251 + } 252 + 253 + .cal-map-popup .leaflet-popup-content { 254 + margin: 8px 12px; 255 + line-height: 1.5; 256 + } 257 + 258 + .cal-popup-event { 259 + margin-bottom: 6px; 260 + } 261 + 262 + .cal-popup-event:last-child { 263 + margin-bottom: 0; 264 + } 265 + 266 + .cal-popup-event a { 267 + color: var(--text-accent); 268 + text-decoration: none; 269 + } 270 + 271 + .cal-popup-event a:hover { 272 + text-decoration: underline; 273 + } 274 + 275 + .cal-popup-venue { 276 + color: var(--text-muted); 277 + font-size: 11px; 278 + } 279 + 280 + /* Highlight ring around selected marker */ 281 + .cal-marker-highlight { 282 + animation: cal-pulse 1.5s ease-out; 283 + } 284 + 285 + @keyframes cal-pulse { 286 + 0% { 287 + stroke-opacity: 0.8; 288 + fill-opacity: 0.3; 289 + } 290 + 100% { 291 + stroke-opacity: 0.3; 292 + fill-opacity: 0.1; 293 + } 294 + } 295 + 296 + /* Leaflet zoom/attribution tweaks for sidebar */ 297 + .cal-pane-bottom .leaflet-control-zoom { 298 + margin-top: 6px; 299 + margin-right: 6px; 300 + } 301 + 302 + .cal-pane-bottom .leaflet-control-attribution { 303 + font-size: 9px; 304 + background: var(--background-primary-alt) !important; 305 + color: var(--text-faint); 306 + } 307 + 308 + .cal-pane-bottom .leaflet-control-attribution a { 309 + color: var(--text-muted); 310 + } 311 + 312 + /* Fix Leaflet container background for dark themes */ 313 + .cal-pane-bottom .leaflet-container { 314 + background: var(--background-primary-alt); 315 + }