feat: archive CW-033 deployed sign map prototype v0.1.0

This commit is contained in:
mpmedia committed 2026-10-28 15:15:26 -05:00
commit 2f1c437a55
17 files changed
+1562

No files matched your search

+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env python3
"""Read-only Vontas GetStop adapter. Never logs authenticated request URLs."""
import argparse,datetime,json,os,pathlib,re,time,urllib.parse,urllib.request
p=argparse.ArgumentParser();p.add_argument('--players',type=pathlib.Path,required=True);p.add_argument('--output',type=pathlib.Path,required=True);a=p.parse_args()
base=os.environ.get('RTC_NATIVE_API_BASE','').rstrip('/');key=os.environ.get('RTC_NATIVE_API_KEY','')
if not base or not key:raise SystemExit('Set RTC_NATIVE_API_BASE (ASMX service URL) and RTC_NATIVE_API_KEY securely.')
url=urllib.parse.urlsplit(base)
if url.scheme!='https' or url.query or url.fragment:raise SystemExit('API base must be HTTPS with no credential query or fragment.')
raw=json.loads(a.players.read_text());players=raw['rows'] if isinstance(raw,dict) else raw;ids=set()
for player in players:
m=re.search(r'-\s*S(\d+(?:\s*,\s*\d+)*)\s*$',player.get('player',''),re.I)
if m:ids.update(re.findall(r'\d+',m[1]))
results={};errors={}
for sid in sorted(ids,key=int):
try:
request=base+'/GetStop?'+urllib.parse.urlencode({'apikey':key,'stopid':sid})
with urllib.request.urlopen(request,timeout=25) as response:stop=json.load(response)
if not isinstance(stop,dict) or str(stop.get('StopID'))!=sid or stop.get('SrcStopID') is None:errors[sid]='Invalid stop response'
else:results[sid]=stop
except Exception as exc:errors[sid]=type(exc).__name__
time.sleep(.1)
report={'endpoint':base+'/GetStop','retrieved_at_utc':datetime.datetime.now(datetime.timezone.utc).isoformat(),'stops':results,'errors':errors}
a.output.parent.mkdir(parents=True,exist_ok=True);a.output.write_text(json.dumps(report,indent=2));print('Read',len(results),'stops;',len(errors),'errors. Credentials excluded.')
if errors:raise SystemExit(2)
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Build normalized map data from read-only OnSign export, native stops, and a dated GTFS ZIP."""
import argparse,collections,csv,datetime,difflib,hashlib,io,json,math,pathlib,re,zipfile
p=argparse.ArgumentParser();p.add_argument('--players',type=pathlib.Path,required=True);p.add_argument('--native',type=pathlib.Path,required=True);p.add_argument('--gtfs',type=pathlib.Path,required=True);p.add_argument('--service-date',type=datetime.date.fromisoformat,required=True);p.add_argument('--output-dir',type=pathlib.Path,required=True);a=p.parse_args()
z=zipfile.ZipFile(a.gtfs)
def rows(name):return list(csv.DictReader(io.TextIOWrapper(z.open(name+'.txt'),encoding='utf-8-sig')))
stops={r['stop_id']:r for r in rows('stops')};trips={r['trip_id']:r for r in rows('trips')};routes=rows('routes');feed=rows('feed_info')[0];ds=a.service_date.strftime('%Y%m%d');day=a.service_date.strftime('%A').lower()
if not feed.get('feed_start_date','00000000')<=ds<=feed.get('feed_end_date','99999999'):raise SystemExit('Requested date is outside feed validity. Download the current official feed before a live rebuild.')
active={r['service_id'] for r in rows('calendar') if r['start_date']<=ds<=r['end_date'] and r[day]=='1'}
for c in rows('calendar_dates'):
if c['date']==ds:
if c['exception_type']=='1':active.add(c['service_id'])
else:active.discard(c['service_id'])
if not active:raise SystemExit('No service active on requested date.')
sr=collections.defaultdict(set);rs=collections.defaultdict(set);shapes=collections.defaultdict(list)
for st in rows('stop_times'):
t=trips[st['trip_id']]
if t['service_id'] in active:sr[st['stop_id']].add(t['route_id'])
for t in trips.values():
if t['service_id'] in active and t['shape_id']:rs[t['route_id']].add(t['shape_id'])
for sh in rows('shapes'):shapes[sh['shape_id']].append((int(sh['shape_pt_sequence']),float(sh['shape_pt_lat']),float(sh['shape_pt_lon'])))
payload=json.loads(a.players.read_text());native=json.loads(a.native.read_text());players=payload['rows'];audit=[];used=set()
def distance(coords,s):
lat,lon=coords.get('latitude'),coords.get('longitude')
if lat is None or lon is None:return None
x,y=float(s['stop_lat']),float(s['stop_lon']);rad=math.pi/180;h=math.sin((lat-x)*rad/2)**2+math.cos(lat*rad)*math.cos(x*rad)*math.sin((lon-y)*rad/2)**2
return round(6371000*2*math.asin(min(1,math.sqrt(h))),2)
for player in players:
m=re.search(r'-\s*S(\d+(?:\s*,\s*\d+)*)\s*$',player['player'],re.I);ids=re.findall(r'\d+',m[1]) if m else []
player.update(source_ids=ids,kind='kiosk' if 'kiosk' in player['player'].lower() else 'sign',excluded=not bool(m),exclude_reason='Detached test player' if player.get('detached') else ('Showroom or unassigned player; no field stop ID' if not m else ''),crosswalk=[])
for sid in ids:
n=native['stops'].get(sid);target=str(n.get('SrcStopID')) if n else ''
if not n or str(n.get('StopID'))!=sid or target not in stops:raise SystemExit('Unresolved native stop '+sid+'; do not guess an ID, coordinate, or route.')
s=stops[target];geo=n.get('GeoLoc') or {};delta=distance(geo,s);norm=lambda v:re.sub('[^a-z0-9]','',v.lower());ratio=difflib.SequenceMatcher(None,norm(n.get('Name','')),norm(s['stop_name'])).ratio()
c={'native_stop_id':sid,'gtfs_stop_id':target,'native_name':n.get('Name'),'gtfs_name':s['stop_name'],'native_coordinates':[geo.get('latitude'),geo.get('longitude')],'gtfs_coordinates':[float(s['stop_lat']),float(s['stop_lon'])],'coordinate_delta_m':delta,'coordinate_review':delta is not None and delta>25,'name_review':ratio<.85,'routes':sorted(sr[target]),'basis':'Native GetStop SrcStopID → GTFS stop_id'};player['crosswalk'].append(c)
player['resolved_stop_ids']=[c['gtfs_stop_id'] for c in player['crosswalk']]
player['candidate_ids']=player['resolved_stop_ids'][:];player['match']='excluded' if player['excluded'] else 'API verified'
if player['kind']=='kiosk':
parents={stops[t]['parent_station'] for t in player['resolved_stop_ids']}
if len(parents)!=1 or not next(iter(parents)) or next(iter(parents)) not in stops:raise SystemExit('Kiosk bays do not resolve to exactly one GTFS parent station; manual review required.')
player['candidate_ids']=sorted(parents);player['match']='API verified · station reference'
player['routes']=sorted(set().union(*(sr[t] for t in player['resolved_stop_ids']))) if ids else [];player['coordinate_review']=any(c['coordinate_review'] for c in player['crosswalk']);used.update(player['candidate_ids']+player['resolved_stop_ids'])
for c in player['crosswalk']:
audit.append({'player_id':player['player_id'],'player_name':player['player'],'status':player['status'],'native_id':c['native_stop_id'],'id_result':'resolved via native API','gtfs_stop_id':c['gtfs_stop_id'],'gtfs_name':c['gtfs_name'],'native_name':c['native_name'],'mapping_basis':c['basis'],'gtfs_lat':c['gtfs_coordinates'][0],'gtfs_lon':c['gtfs_coordinates'][1],'coordinate_delta_m':c['coordinate_delta_m'],'coordinate_review':c['coordinate_review'],'name_review':c['name_review'],'station_reference':player['candidate_ids'][0] if player['kind']=='kiosk' else ''})
route_data=[{'id':r['route_id'],'short':r['route_short_name'],'name':r['route_long_name'],'color':'#'+r['route_color'],'shape_ids':sorted(rs[r['route_id']]),'lines':[[[lat,lon] for _,lat,lon in sorted(shapes[sh])] for sh in sorted(rs[r['route_id']])]} for r in routes]
meta={'account':players[0].get('account_id') if players else None,'as_of':payload.get('as_of'),'cache_age_s':payload.get('cache_age_s'),'service_date':a.service_date.isoformat(),'feed_info':feed,'feed_url':'https://data.trilliumtransit.com/gtfs/rtc-nv-us/rtc-nv-us.zip','official_page':'https://rtcwashoe.com/resources-and-reports/','sha256':hashlib.sha256(a.gtfs.read_bytes()).hexdigest(),'active_services':sorted(active),'native_retrieved_at_utc':native['retrieved_at_utc'],'resolved_native_ids':len(audit),'coordinate_reviews':sum(c['coordinate_review'] for c in audit),'unmatched':0,'ambiguous_signs':0,'coordinate_authority':'GTFS stops.txt only; OnSign location unused'}
d={'meta':meta,'players':players,'stops':{sid:{'id':sid,'name':stops[sid]['stop_name'],'lat':float(stops[sid]['stop_lat']),'lon':float(stops[sid]['stop_lon']),'routes':sorted(sr[sid]),'headings':[]} for sid in used},'routes':route_data,'audit':audit}
a.output_dir.mkdir(parents=True,exist_ok=True);(a.output_dir/'rtc-map-data.json').write_text(json.dumps(d,separators=(',',':')))
with (a.output_dir/'resolved-stop-crosswalk.csv').open('w') as f:
w=csv.DictWriter(f,fieldnames=list(audit[0]));w.writeheader();w.writerows(audit)
print('Prepared',len(players),'players,',len(audit),'native stop mappings,',len(route_data),'GTFS routes')
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""Render the captured map from validated normalized data. No credentials are bundled by default."""
import argparse,json,os,pathlib
ROOT=pathlib.Path(__file__).resolve().parents[1]
p=argparse.ArgumentParser();p.add_argument('--data',type=pathlib.Path,required=True);p.add_argument('--output',type=pathlib.Path,required=True);p.add_argument('--include-carto-key',action='store_true');a=p.parse_args()
s=(ROOT/'templates/map.html').read_text();key=os.environ.get('RTC_CARTO_BASEMAP_KEY','') if a.include_carto_key else ''
# Retain the prototype's RTC-specific UI; this renderer does not claim multi-agency support.
s=s.replace('const CARTO_BASEMAP_KEY=','let CARTO_BASEMAP_KEY=')
s=s.replace("baseMode=mode;$('basemap').value=mode;","if(mode.startsWith('positron')&&!CARTO_BASEMAP_KEY){const k=window.prompt('Enter your CARTO basemap key (kept in this tab only):');if(!k){$('basemap').value=baseMode;return;}CARTO_BASEMAP_KEY=k.trim();}baseMode=mode;$('basemap').value=mode;")
if not key:
s=s.replace("let savedBase='positron';","let savedBase='esri';").replace("||savedBase}catch{}setBasemap(","||savedBase}catch{}if(savedBase.startsWith('positron')&&!CARTO_BASEMAP_KEY)savedBase='esri';setBasemap(")
files={'/*LEAFLET_CSS*/':'leaflet.css','/*MAPLIBRE_CSS*/':'maplibre-gl.css','/*LEAFLET_JS*/':'leaflet.js','/*MAPLIBRE_JS*/':'maplibre-gl.js','/*MAPLIBRE_LEAFLET_JS*/':'leaflet-maplibre-gl.js'}
for token,name in files.items():s=s.replace(token,(ROOT/'vendor'/name).read_text().replace('</script>','<\\/script>'))
s=s.replace('/*DATA*/',json.dumps(json.loads(a.data.read_text()),separators=(',',':')).replace('</script>','<\\/script>')).replace('/*CARTO_KEY*/',json.dumps(key))
a.output.parent.mkdir(parents=True,exist_ok=True);a.output.write_text(s);print('Rendered',a.output,'with embedded CARTO key' if key else 'without embedded credentials')
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env python3
"""Validate map coordinate, ID, service and shape integrity against the captured source inputs."""
import argparse,collections,csv,io,json,pathlib,zipfile
p=argparse.ArgumentParser();p.add_argument('--data',type=pathlib.Path,required=True);p.add_argument('--native',type=pathlib.Path,required=True);p.add_argument('--gtfs',type=pathlib.Path,required=True);a=p.parse_args();d=json.loads(a.data.read_text());n=json.loads(a.native.read_text());z=zipfile.ZipFile(a.gtfs)
def rows(name):return list(csv.DictReader(io.TextIOWrapper(z.open(name+'.txt'),encoding='utf-8-sig')))
stops={r['stop_id']:r for r in rows('stops')};trips={r['trip_id']:r for r in rows('trips')};routes={r['route_id']:r for r in rows('routes')};active=set(d['meta']['active_services']);sr=collections.defaultdict(set);sh=collections.defaultdict(list);rs=collections.defaultdict(set)
for r in rows('stop_times'):
if trips[r['trip_id']]['service_id'] in active:sr[r['stop_id']].add(trips[r['trip_id']]['route_id'])
for t in trips.values():
if t['service_id'] in active and t['shape_id']:rs[t['route_id']].add(t['shape_id'])
for r in rows('shapes'):sh[r['shape_id']].append((int(r['shape_pt_sequence']),float(r['shape_pt_lat']),float(r['shape_pt_lon'])))
for player in d['players']:
assert player['resolved_stop_ids']==[str(n['stops'][sid]['SrcStopID']) for sid in player['source_ids']]
assert set(player['routes'])==(set().union(*(sr[s] for s in player['resolved_stop_ids'])) if player['source_ids'] else set())
if player['kind']=='kiosk':assert all(stops[s]['parent_station']==player['candidate_ids'][0] for s in player['resolved_stop_ids'])
for sid,s in d['stops'].items():assert [s['lat'],s['lon']]==[float(stops[sid]['stop_lat']),float(stops[sid]['stop_lon'])]
for r in d['routes']:
assert r['color']=='#'+routes[r['id']]['route_color'] and set(r['shape_ids'])==rs[r['id']]
for sid,line in zip(r['shape_ids'],r['lines']):assert line==[[lat,lon] for _,lat,lon in sorted(sh[sid])]
assert len(d['audit'])==sum(len(p['source_ids']) for p in d['players'])
print('PASS: native ID joins, GTFS coordinates, mapped-bay routes, active-service geometry and route colors')
+59
View File
@@ -0,0 +1,59 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Deployed Sign Map</title><style>/*LEAFLET_CSS*/</style><style>/*MAPLIBRE_CSS*/</style><style>
*{box-sizing:border-box}body{margin:0;background:#f3f5f7;color:#162a38;font:14px/1.45 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}button,input,select{font:inherit}button,a{cursor:pointer}button{border:1px solid #ced8df;background:white;border-radius:7px;padding:7px 10px;color:#19384b}button:hover{background:#e7f0f5}button:focus-visible,input:focus-visible,select:focus-visible{outline:3px solid #0673b5}header{padding:14px 22px;background:#122f40;color:white;display:flex;justify-content:space-between;align-items:center;gap:15px}h1{margin:0;font-size:22px;font-weight:650}header p{margin:3px 0 0;color:#c7d8e2;font-size:12px}.topstats{display:flex;gap:18px;white-space:nowrap}.topstats b{font-size:22px;display:block}.topstats span{color:#c7d8e2;font-size:12px}.notice{padding:10px 22px;background:#fff2cf;color:#624a0d;border-bottom:1px solid #e7d4a1;font-size:13px}.shell{display:grid;grid-template-columns:350px minmax(0,1fr);height:calc(100vh - 145px);min-height:550px}aside{background:#fff;overflow:auto;border-right:1px solid #d3dce2}.controls{padding:13px;position:sticky;top:0;background:white;z-index:3;border-bottom:1px solid #dae1e6}.controls input{width:100%;padding:8px;border:1px solid #c9d5de;border-radius:6px;margin-bottom:8px}.controls select{padding:6px;border:1px solid #c9d5de;border-radius:5px;width:100%}.listcount{font-size:12px;color:#566e7e;margin-top:8px}.player{display:flex;gap:10px;width:100%;text-align:left;border:0;border-radius:0;border-bottom:1px solid #e5ebef;padding:12px 14px;background:white}.player.selected{background:#e5f1f8;border-left:4px solid #137daf;padding-left:10px}.player strong{font-size:13px;display:block;font-weight:600}.player small{display:block;color:#596b76;font-size:12px;margin-top:3px}.statusicon{display:inline-flex;flex:0 0 auto;width:26px;height:26px;align-items:center;justify-content:center;border:2px solid white;box-shadow:0 1px 4px #0006;background:#17815a;color:white;border-radius:50%;font-size:16px;font-weight:bold}.statusicon.offline{background:#b7303f}.statusicon.kiosk{border-radius:5px;background:#226baf;position:relative}.statusicon.kiosk::after{content:"";position:absolute;right:-3px;bottom:-3px;width:9px;height:9px;background:#17815a;border:2px solid white;border-radius:50%}.statusicon.kiosk.offline::after{background:#b7303f}.statusicon.excluded{background:#6c7781;border-radius:5px}.statusicon.ambiguous{background:white;color:#986609;border:3px dashed #b37a12}.statusicon.ambiguous.offline{color:#b7303f;border-color:#b7303f}.markerwrap{background:none;border:none}.mapcol{position:relative;min-width:0;display:flex;flex-direction:column}#map{flex:1;min-height:350px;background:#e5ebef}.mapbar{display:flex;gap:7px;padding:9px 12px;background:#fff;border-bottom:1px solid #dae1e6;flex-wrap:wrap;align-items:center}.legend{font-size:12px;color:#3f5868;margin-left:auto}.legend i{display:inline-block;width:9px;height:9px;border-radius:50%;background:#17815a;margin-left:10px}.legend .red{background:#b7303f}.legend .blue{background:#226baf;border-radius:1px}.legend .amber{background:white;border:2px dashed #b37a12;width:12px;height:12px}.routepanel{padding:10px 12px;background:#fff;max-height:150px;overflow:auto;border-top:1px solid #dae1e6}.routepanel summary{cursor:pointer;font-weight:600}.routegrid{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}.routechip{display:flex;align-items:center;gap:5px;font-size:12px;padding:3px 6px;border:1px solid #dce3e7;border-radius:5px}.routechip input{margin:0}.swatch{height:4px;width:20px;display:inline-block}.detail{position:absolute;right:14px;top:66px;width:370px;max-width:calc(100% - 28px);max-height:calc(100% - 135px);overflow:auto;background:white;box-shadow:0 8px 30px #1236;z-index:600;border-radius:10px;padding:18px}.detail h2{font-size:17px;margin:0 28px 10px 0}.detail h3{font-size:14px;margin:17px 0 5px}.detail p{margin:8px 0;font-size:13px}.detail dl{display:grid;grid-template-columns:105px 1fr;gap:6px;font-size:12px}.detail dt{color:#5b707e}.detail dd{margin:0;overflow-wrap:anywhere}.close{position:absolute;right:8px;top:8px;border:0;padding:3px 8px;font-size:21px}.badge{padding:3px 6px;background:#fff1c8;border-radius:4px;display:inline-block;font-size:12px}.stopcard{border-top:1px solid #e0e6eb;padding-top:8px;margin-top:8px;font-size:13px}.routebadge{display:inline-block;border-bottom:3px solid var(--route);margin:2px 5px 2px 0;padding:2px;color:#263f50;font-size:12px}dialog{border:0;border-radius:10px;width:1000px;max-width:94vw;max-height:90vh;padding:24px;color:#163043}dialog::backdrop{background:#102c40aa}dialog h2{margin:0 30px 12px 0}table{border-collapse:collapse;font-size:12px;width:100%}th,td{border-bottom:1px solid #dce3e7;text-align:left;padding:8px;vertical-align:top}th{background:#edf2f5;position:sticky;top:0}.scrolltable{max-height:52vh;overflow:auto}.muted{color:#617583;font-size:12px}.leaflet-tooltip{font:12px/1.4 -apple-system,sans-serif;max-width:300px;white-space:normal}.leaflet-controlLine truncated
.navigation{display:flex;flex-direction:column;gap:7px;margin:10px 0}.navigation a{display:block;padding:10px 12px;border:1px solid #b8cedb;border-radius:6px;text-decoration:none;color:#075c8e;background:#f1f8fc;font-size:13px;min-height:42px}.navigation a:hover{background:#dfedf5}.basemaplabel{display:flex;align-items:center;gap:7px;font-size:12px;font-weight:600}.basemaplabel select{padding:7px;border:1px solid #b8ccd9;border-radius:6px;background:white;color:#163c51;font-size:13px}.leaflet-gl-layer{pointer-events:none}.maplibregl-control-container{display:none}</style></head><body>
<header><div><h1>Deployed Sign Map</h1><p>Captured transit-sign deployment snapshot</p></div><div class="topstats"><div><b id="stat-field">—</b><span>field players</span></div><div><b id="stat-health">—</b><span>online / offline in field</span></div><div><b id="stat-excluded">—</b><span>excluded</span></div></div></header>
<div class="notice"><strong>Location evidence:</strong> Native stop IDs resolved through CAD/AVL → GTFS. Marker and navigation coordinates come only from GTFS. Two kiosks use station reference points; their bay IDs are resolved.</div>
<div class="shell"><aside><div class="controls"><input id="search" aria-label="Search players, stop IDs or GTFS names" placeholder="Search players, stop IDs or names…"><select id="filter" aria-label="Filter players"><option value="field">All field players</option><option value="offline">Offline field players</option><option value="kiosk">Kiosks</option><option value="review">Coordinate differences to review</option><option value="excluded">Excluded test / showroom</option><option value="all">All account players</option></select><div id="listcount" class="listcount"></div></div><div id="players"></div></aside>
<main class="mapcol"><div class="mapbar"><label class="basemaplabel">Basemap <select id="basemap" aria-label="Basemap"><option value="positron">Positron · street labels</option><option value="positron-nolabels">Positron · no labels</option><option value="esri">Esri Gray Canvas</option><option value="osm">Original street map</option><option value="plain">Plain background</option></select></label><button id="fit">Fit field locations</button><button id="clear">Clear selection</button><button id="report">ID report</button><button id="sources">Sources & dates</button><div class="legend"><i></i> online <i class="red"></i> offline <i class="blue"></i> kiosk ▧ </div></div><div id="map" role="region" aria-label="Interactive map of RTC Washoe deployed sign reference locations"></div><div id="basehint">Street tiles need internet. Embedded routes and markers work offline.</div><section id="detail" class="detail hidden" aria-label="Selected player details"></section><details class="routepanel"><summary>Route lines · GTFS colors · service October 28, 2026</summary><div><button id="routesall">All routes</button> <button id="routesnone">Hide routes</button> <span class="muted">Select a player to emphasize routes serving its resolved stop(s). Kiosk routes come from the mapped bays only.</span></div><div id="routegrid" class="routegrid"></div></details></main></div>
<dialog id="reportdialog"><button class="close" aria-label="Close report">×</button><h2>Resolved native stop-ID crosswalk</h2><p><strong>Native stop IDs resolved by explicit source IDs.</strong> Each native GetStop response supplies SrcStopID, which joins to current GTFS stop_id. Names and coordinate differences are included for review. Native IDs must not be assumed to share the GTFS identifier namespace.</p><p>Marker and navigation coordinates always use GTFS stops.txt. Native API coordinates are retained only for comparison; OnSign locations are unused. Native stops that differ from GTFS by more than 25 metres are flagged for coordinate review. This does not invalidate their explicit ID mapping. Kiosk device positions remain unknown; station reference points and mapped bays are clearly labeled.</p><button id="csvdownload">Download full CSV crosswalk</button><div class="scrolltable"><table><thead><tr><th>Player / native ID</th><th>Verified GTFS stop</th><th>Coordinate evidence</th></tr></thead><tbody id="auditbody"></tbody></table></div></dialog>
<dialog id="sourcesdialog"><button class="close" aria-label="Close sources">×</button><h2>Sources, dates & limits</h2><div id="sourcebody"></div></dialog>
<script>/*LEAFLET_JS*/</script><script>/*MAPLIBRE_JS*/</script><script>/*MAPLIBRE_LEAFLET_JS*/</script><script>const DATA=/*DATA*/;
const $=id=>document.getElementById(id),esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
const map=L.map('map',{preferCanvas:true,minZoom:1,maxZoom:20}).setView([39.519,-119.79],12), routeLayers={}, markerItems=[];let selected=null;const enabled=new Set(DATA.routes.map(r=>r.id));
const CARTO_BASEMAP_KEY=/*CARTO_KEY*/;
const cartoAttribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors · © <a href="https://carto.com/attribution/">CARTO</a>';
map.createPane('vectorBasemap');map.getPane('vectorBasemap').style.zIndex=200;map.getPane('vectorBasemap').style.pointerEvents='none';
let activeBasemap=null,baseMode='positron',vectorMap=null;
function setBasemap(mode){
baseMode=mode;$('basemap').value=mode;
if(activeBasemap){map.removeLayer(activeBasemap);activeBasemap=null;}vectorMap=null;
$('basehint').textContent=mode==='plain'?'Plain background · embedded routes and stops':mode.startsWith('positron')?'CARTO vector · loading basemap…':'Raster basemap · loading…';
if(mode==='plain'){try{localStorage.setItem('rtc-basemap',mode)}catch{}return;}
if(mode.startsWith('positron')){
try{
const layer=L.maplibreGL({style:'https://basemaps.cartocdn.com/gl/'+mode+'-gl-style/style.json?key='+encodeURIComponent(CARTO_BASEMAP_KEY),pane:'vectorBasemap',interactive:false,attribution:cartoAttribution,attributionControl:{customAttribution:cartoAttribution}});
activeBasemap=layer;layer.addTo(map);const gl=layer.getMaplibreMap();vectorMap=gl;
gl.on('style.load',()=>{if(activeBasemap!==layer)return;gl.getStyle().layers.forEach(l=>{if(l.type==='symbol'&&(mode.endsWith('nolabels')||/poi|housenumber/i.test(l.id)||/poi/i.test(l['source-layer']||'')))gl.setLayoutProperty(l.id,'visibility','none');});});
gl.on('idle',()=>{if(activeBasemap===layer)$('basehint').textContent=mode==='positron'?'CARTO vector · street labels · POIs hidden':'CARTO vector · no labels';});
gl.on('error',()=>{if(activeBasemap===layer)$('basehint').textContent='Vector basemap could not fully load. Routes and stops remain usable; choose another basemap if needed.';});
}catch{ $('basehint').textContent='Vector rendering unavailable. Choose Esri, original streets, or plain background.';}
}else{
const esri=mode==='esri',layer=L.tileLayer(esri?'https://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}':'https://tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:20,maxNativeZoom:esri?16:19,attribution:esri?'Tiles © Esri · Esri, DeLorme, NAVTEQ':'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'});
activeBasemap=layer;layer.on('load',()=>{if(activeBasemap===layer)$('basehint').textContent=esri?'Esri Gray Canvas · raster tiles':'Original OpenStreetMap · raster tiles';});layer.on('tileerror',()=>{if(activeBasemap===layer)$('basehint').textContent='Basemap tiles unavailable. Embedded routes and stops remain usable.';});layer.addTo(map);
}
try{localStorage.setItem('rtc-basemap',mode)}catch{}
}
$('basemap').onchange=()=>setBasemap($('basemap').value);
let savedBase='positron';try{savedBase=localStorage.getItem('rtc-basemap')||savedBase}catch{}setBasemap(['positron','positron-nolabels','esri','osm','plain'].includes(savedBase)?savedBase:'positron');
L.control.scale({imperial:true,metric:true}).addTo(map);const markerGroup=L.layerGroup().addTo(map);
DATA.routes.forEach(r=>{routeLayers[r.id]=L.polyline(r.lines,{color:r.color,weight:3,opacity:.65}).addTo(map).bindTooltip(esc(r.short+' · '+r.name));routeLayers[r.id].on('click',()=>{if(r.lines.length)map.fitBounds(routeLayers[r.id].getBounds(),{padding:[30,30]});});});
function icon(p){let symbol=p.excluded?'×':p.kind==='kiosk'?'▧':p.status==='offline'?'!':'✓';return `<span class="statusicon ${p.status} ${p.kind} ${p.excluded?'excluded':''} ${p.match==='ambiguous'?'ambiguous':''}">${symbol}</span>`;}
DATA.players.filter(p=>!p.excluded).forEach(p=>p.candidate_ids.forEach(sid=>{const s=DATA.stops[sid],m=L.marker([s.lat,s.lon],{icon:L.divIcon({html:icon(p),className:'markerwrap',iconSize:[28,28],iconAnchor:[14,14]}),title:p.player+' · '+p.status+' · '+p.match,keyboard:true}).bindTooltip(esc(p.player.replace(/ - S[\d,]+$/,''))+`<br>${esc(p.status)} · ${esc(p.match)}<br>GTFS ${sid} · ${esc(s.name)}`).on('click',()=>selectPlayer(p.player_id));markerItems.push({p,sid,m});}));
function matches(p){const f=$('filter').value,q=$('search').value.toLowerCase();let yes=f==='all'||f==='field'&&!p.excluded||f==='offline'&&!p.excluded&&p.status==='offline'||f==='kiosk'&&p.kind==='kiosk'||f==='review'&&p.coordinate_review||f==='excluded'&&p.excluded;return yes&&(!q||[p.player,p.player_id,...p.candidate_ids.map(s=>s+' '+DATA.stops[s].name)].join(' ').toLowerCase().includes(q));}
function render(){const ps=DATA.players.filter(matches);$('listcount').textContent=`${ps.length} players · click a player for evidence and details`;$('players').innerHTML=ps.map(p=>`<button class="player ${selected===p.player_id?'selected':''}" data-player="${p.player_id}">${icon(p)}<span><strong>${esc(p.player.replace(/ - S[\d,]+$/,''))}</strong><small>${esc(p.source_ids.length?'S'+p.source_ids.join(', '):p.exclude_reason)}</small><small>${esc(p.status)} · ${esc(p.match)}${p.coordinate_review?' · coordinate review':''}</small></span></button>`).join('');$('players').querySelectorAll('[data-player]').forEach(b=>b.onclick=()=>selectPlayer(b.dataset.player));markerGroup.clearLayers();markerItems.filter(x=>matches(x.p)).forEach(x=>x.m.addTo(markerGroup));}
function badges(ids){return ids.map(id=>{const r=DATA.routes.find(r=>r.id===id);return `<span class="routebadge" style="--route:${r.color}">${esc(r.short)}</span>`}).join('')||'<span class="muted">No trips on selected service date</span>';}
function styleRoutes(){const p=DATA.players.find(x=>x.player_id===selected);DATA.routes.forEach(r=>{const l=routeLayers[r.id];if(enabled.has(r.id)){if(!map.hasLayer(l))l.addTo(map);const emphasized=!p||p.routes.includes(r.id);l.setStyle({color:r.color,weight:emphasized?(p?5:3):2,opacity:emphasized?.83:.13});if(emphasized)l.bringToFront();}else map.removeLayer(l);});}
function navigation(s){const coords=s.lat+','+s.lon;return `<div class="navigation"><a target="_blank" rel="noopener" href="https://maps.apple.com/?daddr=${encodeURIComponent(coords)}&dirflg=d">Navigate to this stop via Apple Maps ↗</a><a target="_blank" rel="noopener" href="https://www.google.com/maps/dir/?api=1&destination=${encodeURIComponent(coords)}&travelmode=driving">Navigate to this stop via Google Maps ↗</a></div>`;}
function stopCard(sid,c){const s=DATA.stops[sid];return `<div class="stopcard"><strong>${c?'Native S'+esc(c.native_stop_id)+' → ':''}GTFS ${sid} · ${esc(s.name)}</strong><p>GTFS ${s.lat}, ${s.lon}${c?' · native API difference '+c.coordinate_delta_m+' m':''}${c&&c.coordinate_review?' <span class="badge">Coordinate review</span>':''}</p>${badges(s.routes)}${navigation(s)}</div>`;}
function selectPlayer(id){selected=id;const p=DATA.players.find(x=>x.player_id===id);$('detail').classList.remove('hidden');$('detail').innerHTML=`<button class="close" aria-label="Close player details">×</button><h2>${esc(p.player.replace(/ - S[\d,]+$/,''))}</h2><span class="badge">${esc(p.match)}</span><dl><dt>Player ID</dt><dd>${esc(p.player_id)}</dd><dt>Status</dt><dd>${esc(p.status)}${p.detached?' · detached':''}</dd><dt>Last seen</dt><dd>${esc(p.last_seen)}</dd><dt>App / model</dt><dd>${esc(p.app_version)} · ${esc(p.model)}</dd><dt>Errors / warnings</dt><dd>${p.errors_24h} / ${p.warnings_24h} in 24h</dd><dt>Native stop IDs</dt><dd>${esc(p.source_ids.join(', ')||'None')}</dd></dl><h3>Location & route evidence</h3><p>${p.excluded?esc(p.exclude_reason)+'. No coordinates assigned.':p.kind==='kiosk'?'All listed native bay IDs are resolved through CAD/AVL SrcStopID to GTFS. The kiosk marker uses the GTFS parent station reference; its exact physical position is unknown. Choose a mapped bay below for stop navigation.':'Native GetStop SrcStopID explicitly identifies this GTFS stop. The marker and navigation links use GTFS coordinates only. OnSign location is unused. This is a bus-stop reference, not a surveyed device position.'}</p>${p.kind==='kiosk'?'<h3>Station reference</h3>'+p.candidate_ids.map(sid=>{const st=DATA.stops[sid];return `<div class="stopcard"><strong>GTFS ${sid} · ${esc(st.name)}</strong><p>Station reference only; not a kiosk GPS location.</p>${navigation(st)}</div>`;}).join('')+'<h3>Resolved bays</h3>':''}${p.crosswalk.map(c=>stopCard(c.gtfs_stop_id,c)).join('')}<h3>${p.kind==='kiosk'?'Routes serving mapped bays':'Routes serving this stop'}</h3>${badges(p.routes)}<p class="muted">Routes joined from verified stop IDs → stop_times → active trips → routes for October 28, 2026. Navigation destinations use GTFS coordinates. Scheduled associations do not establish displayed content or current stop availability.</p>`;$('detail').querySelector('.close').onclick=clearSelection;if(p.candidate_ids.length)map.fitBounds(p.candidate_ids.map(s=>[DATA.stops[s].lat,DATA.stops[s].lon]),{paddingTopLeft:[30,35],paddingBottomRight:[Math.min(400,map.getSize().x*.4),30],maxZoom:16});styleRoutes();render();}
function clearSelection(){selected=null;$('detail').classList.add('hidden');styleRoutes();render();}
function fit(){const coords=markerItems.filter(x=>matches(x.p)).map(x=>[DATA.stops[x.sid].lat,DATA.stops[x.sid].lon]);if(coords.length)map.fitBounds(coords,{padding:[36,36],maxZoom:15});}
$('search').oninput=render;$('filter').onchange=()=>{render();fit()};$('fit').onclick=fit;$('clear').onclick=clearSelection;
$('routegrid').innerHTML=DATA.routes.map(r=>`<label class="routechip" title="${esc(r.name)}"><input type="checkbox" checked value="${r.id}"><span class="swatch" style="background:${r.color}"></span>${esc(r.short)} · ${esc(r.name)}</label>`).join('');$('routegrid').querySelectorAll('input').forEach(c=>c.onchange=()=>{c.checked?enabled.add(c.value):enabled.delete(c.value);styleRoutes();});function routeToggle(all){enabled.clear();DATA.routes.forEach(r=>{if(all)enabled.add(r.id)});$('routegrid').querySelectorAll('input').forEach(c=>c.checked=all);styleRoutes();}$('routesall').onclick=()=>routeToggle(true);$('routesnone').onclick=()=>routeToggle(false);
$('report').onclick=()=>$('reportdialog').showModal();$('sources').onclick=()=>$('sourcesdialog').showModal();document.querySelectorAll('dialog .close').forEach(c=>c.onclick=()=>c.closest('dialog').close());
$('auditbody').innerHTML=DATA.audit.map(a=>`<tr><td>${esc(a.player_name.split(' - S')[0])}<br><b>S${a.native_id}</b></td><td><b>${esc(a.gtfs_stop_id)}</b> ${esc(a.gtfs_name)}<br>${esc(a.mapping_basis)}</td><td>GTFS ${a.gtfs_lat}, ${a.gtfs_lon}<br>Native API difference: ${a.coordinate_delta_m} m${a.coordinate_review?' · REVIEW':''}<br>${a.station_reference?'Kiosk station reference: '+a.station_reference:''}</td></tr>`).join('');
$('csvdownload').onclick=()=>{const keys=Object.keys(DATA.audit[0]),quote=x=>'"'+String(x??'').replace(/"/g,'""')+'"',csv=[keys,...DATA.audit.map(a=>keys.map(k=>a[k]))].map(r=>r.map(quote).join(',')).join('\r\n'),a=document.createElement('a'),url=URL.createObjectURL(new Blob([csv],{type:'text/csv'}));a.href=url;a.download='resolved-stop-crosswalk.csv';a.click();setTimeout(()=>URL.revokeObjectURL(url),5000);};
$('sourcebody').innerHTML=`<p><strong>OnSign:</strong> read-only MCP snapshot. As of ${esc(DATA.meta.as_of)} (cache age ${DATA.meta.cache_age_s}s). All supplied player records are retained, including excluded records. Health is a captured snapshot and does not refresh automatically. OnSign location values are never used.</p><p><strong>Official feed:</strong> linked by <a target="_blank" href="${DATA.meta.official_page}">RTC Washoe’s Resources and Reports page</a>. <a href="${DATA.meta.feed_url}">Download publisher’s current ZIP</a>.</p><p><strong>Verified validity:</strong> feed_info September 5, 2026–January 1, 2027, version 1. HTTP Last-Modified September 8, 2026, 19:07:45 UTC. Downloaded October 28, 2026. October 28 has active weekday service after calendar_dates exceptions. 20 routes, 63 active route/shape combinations; shapes are ordered by shape_pt_sequence and colored with route_color.</p><p><strong>Native crosswalk:</strong> CAD/AVL GetStop responses retrieved ${esc(DATA.meta.native_retrieved_at_utc)}. Native StopID resolves through SrcStopID to GTFS stop_id; names are recorded for corroboration. ${DATA.meta.coordinate_reviews} native stop coordinates differ by more than 25 m; differences are separately reported. All marker and navigation destinations use unmodified GTFS coordinates. Zero unmatched or ambiguous IDs remain.</p><p><strong>Kiosks:</strong> comma-separated IDs resolve individually to station bays. Kiosk markers remain GTFS parent station references, not device coordinates. Route associations use exactly the resolved bays rather than assuming every station bay is assigned.</p><p><strong>Route evidence:</strong> resolved stop IDs → stop_times → active trips → route_id and shape_id. Associations describe GTFS service, not verified OnSign screen content. Native route IDs are not equated to GTFS route IDs. No route is inferred from a nearby line.</p><p><strong>Navigation:</strong> Apple Maps and Google Maps links request driving directions to the selected GTFS stop coordinates. The phone supplies the origin when opened. Street tiles and navigation require internet; embedded map data and controls work offline.</p><p><strong>Basemaps:</strong> CARTO Positron uses vector tiles rendered by MapLibre inside Leaflet. The street-label option hides POIs and house numbers; the no-label option hides all labels. Esri Gray Canvas and original OpenStreetMap are raster alternatives. Plain background needs no basemap connection. Your basemap choice is remembered in this browser. Basemap choice does not alter GTFS coordinates, route colors, or navigation destinations.</p><p><strong>Operating limits:</strong> scheduled geometry; no bus positions, detours, closures or arrival predictions. Native CAD/AVL credentials are excluded. The configured CARTO browser basemap key is used only for basemap requests. No OnSign write operations are included.</p><p class="muted">Feed SHA-256: ${esc(DATA.meta.sha256)}</p>`;
new ResizeObserver(()=>map.invalidateSize({pan:false})).observe($('map'));
const field=DATA.players.filter(p=>!p.excluded);$('stat-field').textContent=field.length;$('stat-health').textContent=field.filter(p=>p.status==='online').length+' / '+field.filter(p=>p.status==='offline').length;$('stat-excluded').textContent=DATA.players.filter(p=>p.excluded).length;render();fit();styleRoutes();window.RTC_MAP={data:DATA,map,selectPlayer,markerItems,routeLayers};
</script></body></html>
+26
View File
@@ -0,0 +1,26 @@
BSD 2-Clause License
Copyright (c) 2010-2023, Volodymyr Agafonkin
Copyright (c) 2010-2011, CloudMade
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+15
View File
@@ -0,0 +1,15 @@
Copyright (c) 2021 MapLibre contributors
Copyright (c) 2014, Mapbox
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+116
View File
@@ -0,0 +1,116 @@
Copyright (c) 2023, MapLibre contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of MapLibre GL JS nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Contains code from mapbox-gl-js v1.13 and earlier
Version v1.13 of mapbox-gl-js and earlier are licensed under a BSD-3-Clause license
Copyright (c) 2020, Mapbox
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Mapbox GL JS nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-------------------------------------------------------------------------------
Contains code from glfx.js
Copyright (C) 2011 by Evan Wallace
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
--------------------------------------------------------------------------------
Contains a portion of d3-color https://github.com/d3/d3-color
Copyright 2010-2016 Mike Bostock
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the author nor the names of contributors may be used to
endorse or promote products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+346
View File
@@ -0,0 +1,346 @@
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['leaflet', 'maplibre-gl'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS-like
module.exports = factory(require('leaflet'), require('maplibre-gl'));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.L, root.maplibregl);
}
}(typeof globalThis !== 'undefined' ? globalThis : this || self, function (L, maplibregl) {
L.MaplibreGL = L.Layer.extend({
options: {
updateInterval: 32,
// How much to extend the overlay view (relative to map size)
// e.g. 0.1 would be 10% of map view in each direction
padding: 0.1,
// whether or not to register the mouse and keyboard
// events on the maplibre overlay
interactive: false,
// set the tilepane as the default pane to draw gl tiles
pane: 'tilePane'
},
initialize: function (options) {
L.setOptions(this, options);
// setup throttling the update event when panning
this._throttledUpdate = L.Util.throttle(this._update, this.options.updateInterval, this);
},
onAdd: function (map) {
if (!this._container) {
this._initContainer();
}
var paneName = this.getPaneName();
map.getPane(paneName).appendChild(this._container);
this._initGL();
this._offset = this._map.containerPointToLayerPoint([0, 0]);
// work around https://github.com/mapbox/mapbox-gl-leaflet/issues/47
if (map.options.zoomAnimation) {
L.DomEvent.on(map._proxy, L.DomUtil.TRANSITION_END, this._transitionEnd, this);
}
},
onRemove: function (map) {
if (this._map._proxy && this._map.options.zoomAnimation) {
L.DomEvent.off(this._map._proxy, L.DomUtil.TRANSITION_END, this._transitionEnd, this);
}
var paneName = this.getPaneName();
map.getPane(paneName).removeChild(this._container);
this._glMap.remove();
this._glMap = null;
},
getEvents: function () {
return {
move: this._throttledUpdate, // sensibly throttle updating while panning
zoomanim: this._animateZoom, // applys the zoom animation to the <canvas>
zoom: this._pinchZoom, // animate every zoom event for smoother pinch-zooming
zoomstart: this._zoomStart, // flag starting a zoom to disable panning
zoomend: this._zoomEnd,
resize: this._resize
};
},
// https://leafletjs.com/reference.html#layer-getattribution
getAttribution: function () {
// Return custom attribution if specified in options
if (this.options.attributionControl) {
return this.options.attributionControl.customAttribution;
}
// Gather attributions from MapLibre styles
var map = this._glMap;
if (map && this.options.attributionControl !== false) {
var style = map.getStyle();
if (style && style.sources) {
return Object.keys(style.sources)
.map(function (sourceId) {
var source = map.getSource(sourceId);
return (source && typeof source.attribution === 'string') ? source.attribution.trim() : null;
})
.filter(Boolean) // Remove null/undefined values
.join(', ');
}
}
return '';
},
getMaplibreMap: function () {
return this._glMap;
},
getCanvas: function () {
return this._glMap.getCanvas();
},
getSize: function () {
return this._map.getSize().multiplyBy(1 + this.options.padding * 2);
},
getBounds: function () {
var halfSize = this.getSize().multiplyBy(0.5);
var center = this._map.latLngToContainerPoint(this._map.getCenter());
return L.latLngBounds(
this._map.containerPointToLatLng(center.subtract(halfSize)),
this._map.containerPointToLatLng(center.add(halfSize))
);
},
getContainer: function () {
return this._container;
},
// returns the pane name set in options if it is a valid pane, defaults to tilePane
getPaneName: function () {
return this._map.getPane(this.options.pane) ? this.options.pane : 'tilePane';
},
_roundPoint: function (p) {
return { x: Math.round(p.x), y: Math.round(p.y) };
},
_initContainer: function () {
var container = this._container = L.DomUtil.create('div', 'leaflet-gl-layer');
this._resizeContainer();
var offset = this._map.getSize().multiplyBy(this.options.padding);
var topLeft = this._map.containerPointToLayerPoint([0, 0]).subtract(offset);
L.DomUtil.setPosition(container, this._roundPoint(topLeft));
},
_resizeContainer: function () {
var size = this.getSize();
this._container.style.width = size.x + 'px';
this._container.style.height = size.y + 'px';
},
_initGL: function () {
var center = this._map.getCenter();
var options = L.extend({}, this.options, {
container: this._container,
center: [center.lng, center.lat],
zoom: this._map.getZoom() - 1,
attributionControl: false
});
this._glMap = new maplibregl.Map(options);
var _map = this._map;
var _currentAttribution = this.getAttribution();
var _getAttribution = this.getAttribution.bind(this);
this._glMap.on('load', function () {
// Force attribution update
if (_map && _map.attributionControl) {
_map.attributionControl.removeAttribution(_currentAttribution);
_map.attributionControl.addAttribution(_getAttribution());
}
});
// allow GL base map to pan beyond min/max latitudes
// Defensively check if properties are writable before setting them,
// ensuring compatibility with both old and new versions of MapLibre GL JS.
var transformProto = Object.getPrototypeOf(this._glMap.transform);
var latRangeDescriptor = Object.getOwnPropertyDescriptor(transformProto, 'latRange');
if (!latRangeDescriptor || latRangeDescriptor.set || latRangeDescriptor.writable) {
this._glMap.transform.latRange = null;
}
// Although this property is obsolete in modern versions, we apply the same
// defensive check for robust backward compatibility.
var maxValidLatitudeDescriptor = Object.getOwnPropertyDescriptor(transformProto, 'maxValidLatitude');
if (!maxValidLatitudeDescriptor || maxValidLatitudeDescriptor.set || maxValidLatitudeDescriptor.writable) {
this._glMap.transform.maxValidLatitude = Infinity;
}
// check for the existence of _helper and _latRange in MapLibre
// this supports MapLibre v5
if (this._glMap.transform._helper && this._glMap.transform._helper._latRange) {
this._glMap.transform._helper._latRange = [-Infinity, Infinity];
}
this._transformGL(this._glMap);
if (this._glMap._canvas.canvas) {
// older versions of mapbox-gl surfaced the canvas differently
this._glMap._actualCanvas = this._glMap._canvas.canvas;
} else {
this._glMap._actualCanvas = this._glMap._canvas;
}
// treat child <canvas> element like L.ImageOverlay
var canvas = this._glMap._actualCanvas;
L.DomUtil.addClass(canvas, 'leaflet-image-layer');
L.DomUtil.addClass(canvas, 'leaflet-zoom-animated');
if (this.options.interactive) {
L.DomUtil.addClass(canvas, 'leaflet-interactive');
}
if (this.options.className) {
L.DomUtil.addClass(canvas, this.options.className);
}
},
_update: function (e) {
if (!this._map) {
return;
}
// update the offset so we can correct for it later when we zoom
this._offset = this._map.containerPointToLayerPoint([0, 0]);
if (this._zooming) {
return;
}
var container = this._container,
gl = this._glMap,
offset = this._map.getSize().multiplyBy(this.options.padding),
topLeft = this._map.containerPointToLayerPoint([0, 0]).subtract(offset);
L.DomUtil.setPosition(container, this._roundPoint(topLeft));
this._transformGL(gl);
},
_transformGL: function (gl) {
var center = this._map.getCenter();
// gl.setView([center.lat, center.lng], this._map.getZoom() - 1, 0);
// calling setView directly causes sync issues because it uses requestAnimFrame
var tr = gl._getTransformForUpdate(); // .clone() ?
if (tr.setCenter) {
// maplibre 5.0.0 and higher:
tr.setCenter(maplibregl.LngLat.convert([center.lng, center.lat]));
tr.setZoom(this._map.getZoom() - 1);
gl.transform.apply(tr);
} else {
// maplibre < 5.0.0
tr = gl.transform;
tr.center = maplibregl.LngLat.convert([center.lng, center.lat]);
tr.zoom = this._map.getZoom() - 1;
}
gl._fireMoveEvents();
},
// update the map constantly during a pinch zoom
_pinchZoom: function (e) {
this._glMap.jumpTo({
zoom: this._map.getZoom() - 1,
center: this._map.getCenter()
});
},
// borrowed from L.ImageOverlay
// https://github.com/Leaflet/Leaflet/blob/master/src/layer/ImageOverlay.js#L139-L144
_animateZoom: function (e) {
var scale = this._map.getZoomScale(e.zoom);
var padding = this._map.getSize().multiplyBy(this.options.padding * scale);
var viewHalf = this.getSize()._divideBy(2);
// corrections for padding (scaled), adapted from
// https://github.com/Leaflet/Leaflet/blob/master/src/map/Map.js#L1490-L1508
var topLeft = this._map.project(e.center, e.zoom)
._subtract(viewHalf)
._add(this._map._getMapPanePos()
.add(padding))._round();
var offset = this._map.project(this._map.getBounds().getNorthWest(), e.zoom)
._subtract(topLeft);
L.DomUtil.setTransform(
this._glMap._actualCanvas,
offset.subtract(this._offset),
scale
);
},
_zoomStart: function (e) {
this._zooming = true;
},
_zoomEnd: function () {
var scale = this._map.getZoomScale(this._map.getZoom());
L.DomUtil.setTransform(
this._glMap._actualCanvas,
// https://github.com/mapbox/mapbox-gl-leaflet/pull/130
null,
scale
);
this._zooming = false;
this._update();
},
_transitionEnd: function (e) {
L.Util.requestAnimFrame(function () {
var zoom = this._map.getZoom();
var center = this._map.getCenter();
var offset = this._map.latLngToContainerPoint(
this._map.getBounds().getNorthWest()
);
this._resizeContainer();
// reset the scale and offset
L.DomUtil.setTransform(this._glMap._actualCanvas, offset, 1);
// enable panning once the gl map is ready again
this._glMap.once('moveend', L.Util.bind(function () {
this._zoomEnd();
}, this));
// update the map position
this._glMap.jumpTo({
center: center,
zoom: zoom - 1
});
}, this);
},
_resize: function (e) {
this._transitionEnd(e);
}
});
L.maplibreGL = function (options) {
return new L.MaplibreGL(options);
};
}));
+661
View File
@@ -0,0 +1,661 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container img.leaflet-tile {
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
mix-blend-mode: plus-lighter;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
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;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-attribution-flag {
display: inline !important;
vertical-align: baseline !important;
width: 1em;
height: 0.6669em;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.8);
text-shadow: 1px 1px #fff;
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.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;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.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);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
}
+6
View File
@@ -0,0 +1,6 @@
/* @preserve
* Leaflet 1.9.4, a JS library for interactive maps. https://leafletjs.com
* (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).leaflet={})}(this,function(t){"use strict";function l(t){for(var e,i,n=1,o=arguments.length;n<o;n++)for(e in i=arguments[n])t[e]=i[e];return t}var R=Object.create||function(t){return N.prototype=t,new N};function N(){}function a(t,e){var i,n=Array.prototype.slice;return t.bind?t.bind.apply(t,n.call(arguments,1)):(i=n.call(arguments,2),function(){return t.apply(e,i.length?i.concat(n.call(arguments)):arguments)})}var D=0;function h(t){return"_leaflet_id"in t||(t._leaflet_id=++D),t._leaflet_id}function j(t,e,i){var n,o,s=function(){n=!1,o&&(r.apply(i,o),o=!1)},r=function(){n?o=arguments:(t.apply(i,arguments),setTimeout(s,e),n=!0)};return r}function H(t,e,i){var n=e[1],e=e[0],o=n-e;return t===n&&i?t:((t-e)%o+o)%o+e}function u(){return!1}function i(t,e){return!1===e?t:(e=Math.pow(10,void 0===e?6:e),Math.round(t*e)/e)}function W(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}function F(t){return W(t).split(/\s+/)}function c(t,e){for(var i in Object.prototype.hasOwnProperty.call(t,"options")||(t.options=t.options?R(t.options):{}),e)t.options[i]=e[i];return t.options}function U(t,e,i){var n,o=[];for(n in t)o.push(encodeURIComponent(i?n.toUpperCase():n)+"="+encodeURIComponent(t[n]));return(e&&-1!==e.indexOf("?")?"&":"?")+o.join("&")}var V=/\{ *([\w_ -]+) *\}/g;function q(t,i){return t.replace(V,function(t,e){e=i[e];if(void 0===e)throw new Error("No value provided for variable "+t);return e="function"==typeof e?e(i):e})}var d=Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)};function G(t,e){for(var i=0;i<t.length;i++)if(t[i]===e)return i;return-1}var K="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=";function Y(t){return window["webkit"+t]||window["moz"+t]||window["ms"+t]}var X=0;function J(t){var e=+new Date,i=Math.max(0,16-(e-X));return X=e+i,window.setTimeout(t,i)}var $=window.requestAnimationFrame||Y("RequestAnimationFrame")||J,Q=window.cancelAnimationFrame||Y("CancelAnimationFrame")||Y("CancelRequestAnimationFrame")||function(t){window.clearTimeout(t)};function x(t,e,i){if(!i||$!==J)return $.call(window,a(t,e));t.call(e)}function r(t){t&&Q.call(window,t)}var tt={__proto__:null,extend:l,create:R,bind:a,get lastId(){return D},stamp:h,throttle:j,wrapNum:H,falseFn:u,formatNum:i,trim:W,splitWords:F,setOptions:c,getParamString:U,template:q,isArray:d,indexOf:G,emptyImageUrl:K,requestFn:$,cancelFn:Q,requestAnimFrame:x,cancelAnimFrame:r};function et(){}et.extend=function(t){function e(){c(this),this.initialize&&this.initialize.apply(this,arguments),this.callInitHooks()}var i,n=e.__super__=this.prototype,o=R(n);for(i in(o.constructor=e).prototype=o,this)Object.prototype.hasOwnProperty.call(this,i)&&"prototype"!==i&&"__super__"!==i&&(e[i]=this[i]);if(t.statics&&l(e,t.statics),t.includes){var s=t.includes;if("undefined"!=typeof L&&L&&L.Mixin){s=d(s)?s:[s];for(var r=0;r<s.length;r++)s[r]===L.Mixin.Events&&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)}l.apply(null,[o].concat(t.includes))}return l(o,t),delete o.statics,delete o.includes,o.options&&(o.options=n.options?R(n.options):{},l(o.options,t.options)),o._initHooks=[],o.callInitHooks=function(){if(!this._initHooksCalled){n.callInitHooks&&n.callInitHooks.call(this),this._initHooksCalled=!0;for(var t=0,e=o._initHooks.length;t<e;t++)o._initHooks[t].call(this)}},e},et.include=function(t){var e=this.prototype.options;return l(this.prototype,t),t.options&&(this.prototype.options=e,this.mergeOptions(t.options)),this},et.mergeOptions=function(t){return l(this.prototype.options,t),this},et.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};return this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i),this};var e={on:function(t,e,i){if("object"==typeof t)for(var n in t)this._on(n,t[n],e);else for(var o=0,s=(t=F(t)).length;o<s;o++)this._on(t[o],e,i);return this},off:function(t,e,i){if(arguments.length)if("object"==typeof t)for(var n in t)this._off(n,t[n],e);else{t=F(t);for(var o=1===arguments.length,s=0,r=t.length;s<r;s++)o?this._off(t[s]):this._off(t[s],e,i)}else delete this._events;return this},_on:function(t,e,i,n){"function"!=typeof e?console.warn("wrong listener type: "+typeof e):!1===this._listens(t,e,i)&&(e={fn:e,ctx:i=i===this?void 0:i},n&&(e.once=!0),this._events=this._events||{},this._events[t]=this._events[t]||[],this._events[t].push(e))},_off:function(t,e,i){var n,o,s;if(this._events&&(n=this._events[t]))if(1===arguments.length){if(this._firingCount)for(o=0,s=n.length;o<s;o++)n[o].fn=u;delete this._events[t]}else"function"!=typeof e?console.warn("wrong listener type: "+typeof e):!1!==(e=this._listens(Line truncated
//# sourceMappingURL=leaflet.js.map
+1
View File
@@ -0,0 +1 @@
.maplibregl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgb(0,0,0,0)}.maplibregl-canvas{left:0;position:absolute;top:0}.maplibregl-map:fullscreen{height:100%;width:100%}.maplibregl-ctrl-group button.maplibregl-ctrl-compass{touch-action:none}.maplibregl-canvas-container.maplibregl-interactive,.maplibregl-ctrl-group button.maplibregl-ctrl-compass{cursor:grab;-webkit-user-select:none;-moz-user-select:none;user-select:none}.maplibregl-canvas-container.maplibregl-interactive.maplibregl-track-pointer{cursor:pointer}.maplibregl-canvas-container.maplibregl-interactive:active,.maplibregl-ctrl-group button.maplibregl-ctrl-compass:active{cursor:grabbing}.maplibregl-canvas-container.maplibregl-touch-zoom-rotate,.maplibregl-canvas-container.maplibregl-touch-zoom-rotate .maplibregl-canvas{touch-action:pan-x pan-y}.maplibregl-canvas-container.maplibregl-touch-drag-pan,.maplibregl-canvas-container.maplibregl-touch-drag-pan .maplibregl-canvas{touch-action:pinch-zoom}.maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan,.maplibregl-canvas-container.maplibregl-touch-zoom-rotate.maplibregl-touch-drag-pan .maplibregl-canvas{touch-action:none}.maplibregl-canvas-container.maplibregl-touch-drag-pan.maplibregl-cooperative-gestures,.maplibregl-canvas-container.maplibregl-touch-drag-pan.maplibregl-cooperative-gestures .maplibregl-canvas{touch-action:pan-x pan-y}.maplibregl-ctrl-bottom-left,.maplibregl-ctrl-bottom-right,.maplibregl-ctrl-top-left,.maplibregl-ctrl-top-right{pointer-events:none;position:absolute;z-index:2}.maplibregl-ctrl-top-left{left:0;top:0}.maplibregl-ctrl-top-right{right:0;top:0}.maplibregl-ctrl-bottom-left{bottom:0;left:0}.maplibregl-ctrl-bottom-right{bottom:0;right:0}.maplibregl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.maplibregl-ctrl-top-left .maplibregl-ctrl{float:left;margin:10px 0 0 10px}.maplibregl-ctrl-top-right .maplibregl-ctrl{float:right;margin:10px 10px 0 0}.maplibregl-ctrl-bottom-left .maplibregl-ctrl{float:left;margin:0 0 10px 10px}.maplibregl-ctrl-bottom-right .maplibregl-ctrl{float:right;margin:0 10px 10px 0}.maplibregl-ctrl-group{background:#fff;border-radius:4px}.maplibregl-ctrl-group:not(:empty){box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (forced-colors:active){.maplibregl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.maplibregl-ctrl-group button{background-color:transparent;border:0;box-sizing:border-box;cursor:pointer;display:block;height:29px;outline:none;padding:0;width:29px}.maplibregl-ctrl-group button+button{border-top:1px solid #ddd}.maplibregl-ctrl button .maplibregl-ctrl-icon{background-position:50%;background-repeat:no-repeat;display:block;height:100%;width:100%}@media (forced-colors:active){.maplibregl-ctrl-icon{background-color:transparent}.maplibregl-ctrl-group button+button{border-top:1px solid ButtonText}}.maplibregl-ctrl button::-moz-focus-inner{border:0;padding:0}.maplibregl-ctrl-attrib-button:focus,.maplibregl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.maplibregl-ctrl button:disabled{cursor:not-allowed}.maplibregl-ctrl button:disabled .maplibregl-ctrl-icon{opacity:.25}@media (hover:hover){.maplibregl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}}.maplibregl-ctrl button:not(:disabled):active{background-color:rgba(0,0,0,.05)}.maplibregl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.maplibregl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.maplibregl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.maplibregl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.maplibregl-ctrl-group button:focus:only-child{border-radius:inherit}.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E")}.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23333' viewBox='0 0 29 29'%3E%3Cpath d='M14.5 8.5c-.75 0-1.5.75-1.5 1.5v3h-3c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h3v3c0 .75.75 1.5 1.5 1.5S16 19.75 16 19v-3h3c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13h-3v-3c0-.75-.75-1.5-1.5-1.5'/%3E%3C/svg%3E")}@media (forced-colors:active){.maplibregl-ctrl button.maplibregl-ctrl-zoom-out .maplibregl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='29' height='29' fill='%23fff' viewBox='0 0 29 29'%3E%3Cpath d='M10 13c-.75 0-1.5.75-1.5 1.5S9.25 16 10 16h9c.75 0 1.5-.75 1.5-1.5S19.75 13 19 13z'/%3E%3C/svg%3E")}.maplibregl-ctrl button.maplibregl-ctrl-zoom-in .maplibregl-ctrl-icon{background-image:url("data:image/svLine truncated
+59
View File
@@ -0,0 +1,59 @@
/**
* MapLibre GL JS
* @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v5.6.2/LICENSE.txt
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.maplibregl = factory());
})(this, (function () { 'use strict';
/* eslint-disable */
var maplibregl = {};
var modules = {};
function define(moduleName, _dependencies, moduleFactory) {
modules[moduleName] = moduleFactory;
// to get the list of modules see generated dist/maplibre-gl-dev.js file (look for `define(` calls)
if (moduleName !== 'index') {
return;
}
// we assume that when an index module is initializing then other modules are loaded already
var workerBundleString = 'var sharedModule = {}; (' + modules.shared + ')(sharedModule); (' + modules.worker + ')(sharedModule);'
var sharedModule = {};
// the order of arguments of a module factory depends on rollup (it decides who is whose dependency)
// to check the correct order, see dist/maplibre-gl-dev.js file (look for `define(` calls)
// we assume that for our 3 chunks it will generate 3 modules and their order is predefined like the following
modules.shared(sharedModule);
modules.index(maplibregl, sharedModule);
if (typeof window !== 'undefined') {
maplibregl.setWorkerUrl(window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })));
}
return maplibregl;
};
define("shared",["exports"],(function(t){"use strict";function e(t,e,r,n){return new(r||(r=Promise))((function(i,s){function a(t){try{l(n.next(t));}catch(t){s(t);}}function o(t){try{l(n.throw(t));}catch(t){s(t);}}function l(t){var e;t.done?i(t.value):(e=t.value,e instanceof r?e:new r((function(t){t(e);}))).then(a,o);}l((n=n.apply(t,e||[])).next());}))}function r(t,e){this.x=t,this.y=e;}function n(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var i,s;"function"==typeof SuppressedError&&SuppressedError,r.prototype={clone(){return new r(this.x,this.y)},add(t){return this.clone()._add(t)},sub(t){return this.clone()._sub(t)},multByPoint(t){return this.clone()._multByPoint(t)},divByPoint(t){return this.clone()._divByPoint(t)},mult(t){return this.clone()._mult(t)},div(t){return this.clone()._div(t)},rotate(t){return this.clone()._rotate(t)},rotateAround(t,e){return this.clone()._rotateAround(t,e)},matMult(t){return this.clone()._matMult(t)},unit(){return this.clone()._unit()},perp(){return this.clone()._perp()},round(){return this.clone()._round()},mag(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals(t){return this.x===t.x&&this.y===t.y},dist(t){return Math.sqrt(this.distSqr(t))},distSqr(t){const e=t.x-this.x,r=t.y-this.y;return e*e+r*r},angle(){return Math.atan2(this.y,this.x)},angleTo(t){return Math.atan2(this.y-t.y,this.x-t.x)},angleWith(t){return this.angleWithSep(t.x,t.y)},angleWithSep(t,e){return Math.atan2(this.x*e-this.y*t,this.x*t+this.y*e)},_matMult(t){const e=t[2]*this.x+t[3]*this.y;return this.x=t[0]*this.x+t[1]*this.y,this.y=e,this},_add(t){return this.x+=t.x,this.y+=t.y,this},_sub(t){return this.x-=t.x,this.y-=t.y,this},_mult(t){return this.x*=t,this.y*=t,this},_div(t){return this.x/=t,this.y/=t,this},_multByPoint(t){return this.x*=t.x,this.y*=t.y,this},_divByPoint(t){return this.x/=t.x,this.y/=t.y,this},_unit(){return this._div(this.mag()),this},_perp(){const t=this.y;return this.y=this.x,this.x=-t,this},_rotate(t){const e=Math.cos(t),r=Math.sin(t),n=r*this.x+e*this.y;return this.x=e*this.x-r*this.y,this.y=n,this},_rotateAround(t,e){const r=Math.cos(t),n=Math.sin(t),i=e.y+n*(this.x-e.x)+r*(this.y-e.y);return this.x=e.x+r*(this.x-e.x)-n*(this.y-e.y),this.y=i,this},_round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},constructor:r},r.convert=function(t){if(t instanceof r)return t;if(Array.isArray(t))return new r(+t[0],+t[1]);if(void 0!==t.x&&void 0!==t.y)return new r(+t.x,+t.y);throw new Error("Expected [x, y] or {x, y} point format")};var a=function(){if(s)return i;function t(t,e,r,n){this.cx=3*t,this.bx=3*(r-t)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*e,this.by=3*(n-e)-this.cy,this.ay=1-this.cy-this.by,this.p1x=t,this.p1y=e,this.p2x=r,this.p2y=n;}return s=1,i=t,t.prototype={sampleCurveX:function(t){return ((this.ax*t+this.bx)*t+this.cx)*t},sampleCurveY:function(t){return ((this.ay*t+this.by)*t+this.cy)*t},sampleCurveDerivativeX:function(t){return (3*this.ax*t+2*this.bx)*t+this.cx},solveCurveX:function(t,e){if(void 0===e&&(e=1e-6),t<0)return 0;if(t>1)return 1;for(var r=t,n=0;n<8;n++){var i=this.sampleCurveX(r)-t;if(Math.abs(i)<e)return r;var s=this.sampleCurveDerivativeX(r);if(Math.abs(s)<1e-6)break;r-=i/s;}var a=0,o=1;for(r=t,n=0;n<20&&(i=this.sampleCurveX(r),!(Math.abs(i-t)<e));n++)t>i?a=r:o=r,r=.5*(o-a)+a;return r},solve:function(t,e){return this.sampleCurveY(this.solveCurveX(t,e))}},i}(),o=n(a);let l,u;function c(){return null==l&&(l="undefined"!=typeof OffscreenCanvas&&new OffscreenCanvas(1,1).getContext("2d")&&"function"==typeof createImageBitmap),l}function h(){if(null==u&&(u=!1,c())){const t=5,e=new OffscreenCanvas(t,t).getContext("2d",{willReadFrequently:!0});if(e){for(let r=0;r<t*t;r++){const n=4*r;e.fillStyle=`rgb(${n},${n+1},${n+2})`,e.fillRect(r%t,Math.floor(r/t),1,1);}const r=e.getImageData(0,0,t,t).data;for(let e=0;e<t*t*4;e++)if(e%4!=3&&r[e]!==e){u=!0;break}}}return u||!1}var p=1e-6,f="undefined"!=typeof Float32Array?Float32Array:Array;function d(){var t=new f(9);return f!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[5]=0,t[6]=0,t[7]=0),t[0]=1,t[4]=1,t[8]=1,t}function y(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=0,t[4]=0,t[5]=1,t[6]=0,t[7]=0,t[8]=0,t[9]=0,t[10]=1,t[11]=0,t[12]=0,t[13]=0,t[14]=0,t[15]=1,t}function m(){var t=new f(3);return f!=Float32Array&&(t[0]=0,t[1]=0,t[2]=0),t}function g(t){return Math.hypot(t[0],t[1],t[2])}function x(t,e,r){var n=new f(3);return n[0]=t,n[1]=e,n[2]=r,n}function v(t,e,r){return t[0]=e[0]+r[0],t[1]=e[1]+r[1],t[2]=e[2]+r[2],t}function b(t,e,r){return t[0]=e[0]*r,t[1]=e[1]*r,t[2]=e[2]*r,t}function w(t,e,r){var n=e[0],i=e[1],s=e[2],a=r[0],o=r[1],l=r[2];return t[0]=i*l-s*o,t[1]=s*a-n*l,t[2]=n*o-i*a,t}Math.hypot||(Math.hypot=function(){for(var t=0,e=arguments.length;e--;)t+=arguments[e]*arguments[e];return Math.sqrt(t)});var _,S=g;function A(t,e,r){var n=e[0],i=e[1],s=e[2],a=e[3];return t[0]=r[0]*n+r[4]*i+r[8]*s+r[12]*a,t[1]=r[1]*n+r[5]*i+r[9]*s+r[13]*a,t[2]=r[2]*n+r[6]*i+r[10]*s+r[14]*a,t[3]=r[Line truncated
define("worker",["./shared"],(function(e){"use strict";class t{constructor(e){this.keyCache={},e&&this.replace(e);}replace(e){this._layerConfigs={},this._layers={},this.update(e,[]);}update(t,i){for(const i of t){this._layerConfigs[i.id]=i;const t=this._layers[i.id]=e.bJ(i);t._featureFilter=e.aa(t.filter),this.keyCache[i.id]&&delete this.keyCache[i.id];}for(const e of i)delete this.keyCache[e],delete this._layerConfigs[e],delete this._layers[e];this.familiesBySource={};const o=e.cC(Object.values(this._layerConfigs),this.keyCache);for(const e of o){const t=e.map((e=>this._layers[e.id])),i=t[0];if("none"===i.visibility)continue;const o=i.source||"";let s=this.familiesBySource[o];s||(s=this.familiesBySource[o]={});const n=i.sourceLayer||"_geojsonTileLayer";let r=s[n];r||(r=s[n]=[]),r.push(t);}}}class i{constructor(t){const i={},o=[];for(const e in t){const s=t[e],n=i[e]={};for(const e in s){const t=s[+e];if(!t||0===t.bitmap.width||0===t.bitmap.height)continue;const i={x:0,y:0,w:t.bitmap.width+2,h:t.bitmap.height+2};o.push(i),n[e]={rect:i,metrics:t.metrics};}}const{w:s,h:n}=e.p(o),r=new e.q({width:s||1,height:n||1});for(const o in t){const s=t[o];for(const t in s){const n=s[+t];if(!n||0===n.bitmap.width||0===n.bitmap.height)continue;const a=i[o][t].rect;e.q.copy(n.bitmap,r,{x:0,y:0},{x:a.x+1,y:a.y+1},n.bitmap);}}this.image=r,this.positions=i;}}e.cD("GlyphAtlas",i);class o{constructor(t){this.tileID=new e.Z(t.tileID.overscaledZ,t.tileID.wrap,t.tileID.canonical.z,t.tileID.canonical.x,t.tileID.canonical.y),this.uid=t.uid,this.zoom=t.zoom,this.pixelRatio=t.pixelRatio,this.tileSize=t.tileSize,this.source=t.source,this.overscaling=this.tileID.overscaleFactor(),this.showCollisionBoxes=t.showCollisionBoxes,this.collectResourceTiming=!!t.collectResourceTiming,this.returnDependencies=!!t.returnDependencies,this.promoteId=t.promoteId,this.inFlightDependencies=[],this.globalState=t.globalState;}parse(t,o,n,r,a){return e._(this,void 0,void 0,(function*(){this.status="parsing",this.data=t,this.collisionBoxArray=new e.a8;const l=new e.cE(Object.keys(t.layers).sort()),c=new e.cF(this.tileID,this.promoteId);c.bucketLayerIDs=[];const u={},h={featureIndex:c,iconDependencies:{},patternDependencies:{},glyphDependencies:{},availableImages:n,subdivisionGranularity:a},d=o.familiesBySource[this.source];for(const i in d){const o=t.layers[i];if(!o)continue;1===o.version&&e.w(`Vector tile source "${this.source}" layer "${i}" does not use vector tile spec v2 and therefore may have some rendering errors.`);const r=l.encode(i),a=[];for(let e=0;e<o.length;e++){const t=o.feature(e),s=c.getId(t,i);a.push({feature:t,id:s,index:e,sourceLayerIndex:r});}for(const t of d[i]){const i=t[0];i.source!==this.source&&e.w(`layer.source = ${i.source} does not equal this.source = ${this.source}`),i.minzoom&&this.zoom<Math.floor(i.minzoom)||i.maxzoom&&this.zoom>=i.maxzoom||"none"!==i.visibility&&(s(t,this.zoom,n),(u[i.id]=i.createBucket({index:c.bucketLayerIDs.length,layers:t,zoom:this.zoom,pixelRatio:this.pixelRatio,overscaling:this.overscaling,collisionBoxArray:this.collisionBoxArray,sourceLayerIndex:r,sourceID:this.source,globalState:this.globalState})).populate(a,h,this.tileID.canonical),c.bucketLayerIDs.push(t.map((e=>e.id))));}}const f=e.bN(h.glyphDependencies,(e=>Object.keys(e).map(Number)));this.inFlightDependencies.forEach((e=>null==e?void 0:e.abort())),this.inFlightDependencies=[];let g=Promise.resolve({});if(Object.keys(f).length){const e=new AbortController;this.inFlightDependencies.push(e),g=r.sendAsync({type:"GG",data:{stacks:f,source:this.source,tileID:this.tileID,type:"glyphs"}},e);}const p=Object.keys(h.iconDependencies);let m=Promise.resolve({});if(p.length){const e=new AbortController;this.inFlightDependencies.push(e),m=r.sendAsync({type:"GI",data:{icons:p,source:this.source,tileID:this.tileID,type:"icons"}},e);}const y=Object.keys(h.patternDependencies);let v=Promise.resolve({});if(y.length){const e=new AbortController;this.inFlightDependencies.push(e),v=r.sendAsync({type:"GI",data:{icons:y,source:this.source,tileID:this.tileID,type:"patterns"}},e);}const[w,x,b]=yield Promise.all([g,m,v]),S=new i(w),M=new e.cG(x,b);for(const t in u){const i=u[t];i instanceof e.a9?(s(i.layers,this.zoom,n),e.cH({bucket:i,glyphMap:w,glyphPositions:S.positions,imageMap:x,imagePositions:M.iconPositions,showCollisionBoxes:this.showCollisionBoxes,canonical:this.tileID.canonical,subdivisionGranularity:h.subdivisionGranularity})):i.hasPattern&&(i instanceof e.cI||i instanceof e.cJ||i instanceof e.cK)&&(s(i.layers,this.zoom,n),i.addFeatures(h,this.tileID.canonical,M.patternPositions));}return this.status="done",{buckets:Object.values(u).filter((e=>!e.isEmpty())),featureIndex:c,collisionBoxArray:this.collisionBoxArray,glyphAtlasImage:S.image,imageAtlas:M,glyphMap:this.returnDependencies?w:null,iconMap:this.returnDependencies?x:null,glyphPositions:this.returnDependencies?S.positions:null}}))}}function s(t,i,o){const s=new e.F(i);for(const e of t)e.recalculate(s,o);}cLine truncated
define("index",["exports","./shared"],(function(e,t){"use strict";var i="5.6.2";function o(){var e=new t.A(4);return t.A!=Float32Array&&(e[1]=0,e[2]=0),e[0]=1,e[3]=1,e}let r,a;const s={now:"undefined"!=typeof performance&&performance&&performance.now?performance.now.bind(performance):Date.now.bind(Date),frame(e,i,o){const r=requestAnimationFrame((e=>{a(),i(e);})),{unsubscribe:a}=t.s(e.signal,"abort",(()=>{a(),cancelAnimationFrame(r),o(t.c());}),!1);},frameAsync(e){return new Promise(((t,i)=>{this.frame(e,t,i);}))},getImageData(e,t=0){return this.getImageCanvasContext(e).getImageData(-t,-t,e.width+2*t,e.height+2*t)},getImageCanvasContext(e){const t=window.document.createElement("canvas"),i=t.getContext("2d",{willReadFrequently:!0});if(!i)throw new Error("failed to create canvas 2d context");return t.width=e.width,t.height=e.height,i.drawImage(e,0,0,e.width,e.height),i},resolveURL:e=>(r||(r=document.createElement("a")),r.href=e,r.href),hardwareConcurrency:"undefined"!=typeof navigator&&navigator.hardwareConcurrency||4,get prefersReducedMotion(){return !!matchMedia&&(null==a&&(a=matchMedia("(prefers-reduced-motion: reduce)")),a.matches)}};class n{static testProp(e){if(!n.docStyle)return e[0];for(let t=0;t<e.length;t++)if(e[t]in n.docStyle)return e[t];return e[0]}static create(e,t,i){const o=window.document.createElement(e);return void 0!==t&&(o.className=t),i&&i.appendChild(o),o}static createNS(e,t){return window.document.createElementNS(e,t)}static disableDrag(){n.docStyle&&n.selectProp&&(n.userSelect=n.docStyle[n.selectProp],n.docStyle[n.selectProp]="none");}static enableDrag(){n.docStyle&&n.selectProp&&(n.docStyle[n.selectProp]=n.userSelect);}static setTransform(e,t){e.style[n.transformProp]=t;}static addEventListener(e,t,i,o={}){e.addEventListener(t,i,"passive"in o?o:o.capture);}static removeEventListener(e,t,i,o={}){e.removeEventListener(t,i,"passive"in o?o:o.capture);}static suppressClickInternal(e){e.preventDefault(),e.stopPropagation(),window.removeEventListener("click",n.suppressClickInternal,!0);}static suppressClick(){window.addEventListener("click",n.suppressClickInternal,!0),window.setTimeout((()=>{window.removeEventListener("click",n.suppressClickInternal,!0);}),0);}static getScale(e){const t=e.getBoundingClientRect();return {x:t.width/e.offsetWidth||1,y:t.height/e.offsetHeight||1,boundingClientRect:t}}static getPoint(e,i,o){const r=i.boundingClientRect;return new t.P((o.clientX-r.left)/i.x-e.clientLeft,(o.clientY-r.top)/i.y-e.clientTop)}static mousePos(e,t){const i=n.getScale(e);return n.getPoint(e,i,t)}static touchPos(e,t){const i=[],o=n.getScale(e);for(let r=0;r<t.length;r++)i.push(n.getPoint(e,o,t[r]));return i}static mouseButton(e){return e.button}static remove(e){e.parentNode&&e.parentNode.removeChild(e);}static sanitize(e){const t=(new DOMParser).parseFromString(e,"text/html").body||document.createElement("body"),i=t.querySelectorAll("script");for(const e of i)e.remove();return n.clean(t),t.innerHTML}static isPossiblyDangerous(e,t){const i=t.replace(/\s+/g,"").toLowerCase();return !(!["src","href","xlink:href"].includes(e)||!i.includes("javascript:")&&!i.includes("data:"))||!!e.startsWith("on")||void 0}static clean(e){const t=e.children;for(const e of t)n.removeAttributes(e),n.clean(e);}static removeAttributes(e){for(const{name:t,value:i}of e.attributes)n.isPossiblyDangerous(t,i)&&e.removeAttribute(t);}}n.docStyle="undefined"!=typeof window&&window.document&&window.document.documentElement.style,n.selectProp=n.testProp(["userSelect","MozUserSelect","WebkitUserSelect","msUserSelect"]),n.transformProp=n.testProp(["transform","WebkitTransform"]);const l={supported:!1,testSupport:function(e){!u&&h&&(d?_(e):c=e);}};let c,h,u=!1,d=!1;function _(e){const t=e.createTexture();e.bindTexture(e.TEXTURE_2D,t);try{if(e.texImage2D(e.TEXTURE_2D,0,e.RGBA,e.RGBA,e.UNSIGNED_BYTE,h),e.isContextLost())return;l.supported=!0;}catch(e){}e.deleteTexture(t),u=!0;}var p;"undefined"!=typeof document&&(h=document.createElement("img"),h.onload=()=>{c&&_(c),c=null,d=!0;},h.onerror=()=>{u=!0,c=null;},h.src="data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAQAAAAfQ//73v/+BiOh/AAA="),function(e){let i,o,r,a;e.resetRequestQueue=()=>{i=[],o=0,r=0,a={};},e.addThrottleControl=e=>{const t=r++;return a[t]=e,t},e.removeThrottleControl=e=>{delete a[e],n();},e.getImage=(e,o,r=!0)=>new Promise(((a,s)=>{l.supported&&(e.headers||(e.headers={}),e.headers.accept="image/webp,*/*"),t.e(e,{type:"image"}),i.push({abortController:o,requestParameters:e,supportImageRefresh:r,state:"queued",onError:e=>{s(e);},onSuccess:e=>{a(e);}}),n();}));const s=e=>t._(this,void 0,void 0,(function*(){e.state="running";const{requestParameters:i,supportImageRefresh:r,onError:a,onSuccess:s,abortController:l}=e,h=!1===r&&!t.i(self)&&!t.g(i.url)&&(!i.headers||Object.keys(i.headers).reduce(((e,t)=>e&&"accept"===t),!0));o++;const u=h?c(i,l):t.m(i,l);try{const i=yield u;delete e.abortController,e.state="completed",i.data instanceof HTMLImageElement||t.b(i.data)?s(Line truncated
//
// Our custom intro provides a specialized "define()" function, called by the
// AMD modules below, that sets up the worker blob URL and then executes the
// main module, storing its exported value as 'maplibregl'
var maplibregl$1 = maplibregl;
return maplibregl$1;
}));
//# sourceMappingURL=maplibre-gl.js.map