diff --git a/src/runtime/components/feed-version-map-viewer.vue b/src/runtime/components/feed-version-map-viewer.vue
index 12d32e41..2c027c68 100644
--- a/src/runtime/components/feed-version-map-viewer.vue
+++ b/src/runtime/components/feed-version-map-viewer.vue
@@ -15,22 +15,24 @@
:zoom="zoom ? zoom : null"
:circle-radius="circleRadius"
:circle-color="circleColor"
+ :enable-hover="enableHover"
+ :highlighted-stop-feature-id="highlightedStopFeatureId"
@set-agency-features="agencyFeatures = $event"
@map-click="mapClick"
@set-zoom="currentZoom = $event"
/>
-
- Select routes
-
- Use your cursor to highlight routes
-
+
Select routes and stops
+
Use your cursor to highlight route lines or stop points.
+
Click for details.
+
@@ -72,6 +74,7 @@ query ($limit: Int=100, $agency_ids: [Int!], $after:Int!=0, $route_ids: [Int!],
stop_id
stop_name
geometry
+ location_type
}
}
agency {
@@ -123,7 +126,15 @@ export default {
zoom: { type: Number, default: null },
enableScrollZoom: { type: Boolean, default: false },
circleRadius: { type: Number, default: 1 },
- circleColor: { type: String, default: '#f03b20' }
+ circleColor: { type: String, default: '#f03b20' },
+ enableHover: {
+ type: Boolean,
+ default: true
+ },
+ highlightedStopFeatureId: {
+ type: [String, Number],
+ default: null
+ }
},
data () {
return {
@@ -173,18 +184,17 @@ export default {
const features = []
for (const feature of this.routes) {
for (const g of feature.route_stops || []) {
- if (!(g.stop.location_type !== 0 || g.stop.location_type !== 2)) {
- continue
+ if (g.stop.location_type === 0 || g.stop.location_type === 2) {
+ const fcopy = Object.assign({}, g.stop)
+ delete fcopy.geometry
+ delete fcopy.__typename
+ features.push({
+ type: 'Feature',
+ geometry: g.stop.geometry,
+ properties: fcopy,
+ id: g.stop.id
+ })
}
- const fcopy = Object.assign({}, g.stop)
- delete fcopy.geometry
- delete fcopy.__typename
- features.push({
- type: 'Feature',
- geometry: g.stop.geometry,
- properties: fcopy,
- id: g.stop.id
- })
}
}
return features
@@ -195,7 +205,7 @@ export default {
this.$emit('setRouteFeatures', v)
},
stopFeatures (v) {
- this.$emit('setSopFeatures', v)
+ this.$emit('setStopFeatures', v)
}
},
methods: {
diff --git a/src/runtime/components/map-layers.js b/src/runtime/components/map-layers.js
index 2d61cd68..e14c2c70 100644
--- a/src/runtime/components/map-layers.js
+++ b/src/runtime/components/map-layers.js
@@ -1,3 +1,5 @@
+import { stopColors } from '../constants/stop-colors'
+
const headways = {
high: 600,
medium: 1200,
@@ -17,19 +19,109 @@ const colors = {
metro: '#ff0000',
metrooutline: '#ffffff',
other: '#E6A615',
- stop: '#007cbf'
+ stop: stopColors.stop,
+ stopNode: stopColors.entrance,
+ stopEntrance: stopColors.entrance,
+ stopGeneric: stopColors.node,
+ stopBoarding: stopColors.boarding
+}
+
+const LocationTypes = {
+ STOP: 0, // Stop/Platform
+ STATION: 1, // Station
+ ENTRANCE: 2, // Entrance/Exit
+ NODE: 3, // Generic Node
+ BOARDING: 4 // Boarding Area
}
const stopLayers = [
+ // Add hover/active layer first
+ {
+ name: 'stop-active',
+ type: 'circle',
+ source: 'stops',
+ minzoom: 14,
+ paint: {
+ 'circle-radius': [
+ 'case',
+ ['boolean', ['feature-state', 'hover'], false],
+ 8,
+ 6
+ ],
+ 'circle-color': [
+ 'case',
+ ['>', ['get', 'location_type'], 1],
+ colors.stopNode,
+ colors.stop
+ ],
+ 'circle-opacity': [
+ 'case',
+ ['boolean', ['feature-state', 'hover'], false],
+ 0.8,
+ 0.0
+ ],
+ 'circle-stroke-width': [
+ 'case',
+ ['boolean', ['feature-state', 'hover'], false],
+ 6,
+ 0
+ ],
+ 'circle-stroke-color': [
+ 'case',
+ ['boolean', ['feature-state', 'hover'], false],
+ colors.active,
+ '#ffffff'
+ ]
+ },
+ filter: [
+ 'any',
+ ['==', ['get', 'location_type'], LocationTypes.STOP],
+ ['==', ['get', 'location_type'], LocationTypes.STATION],
+ ['==', ['get', 'location_type'], LocationTypes.ENTRANCE]
+ ]
+ },
+ // Regular stops layer
{
name: 'stops',
type: 'circle',
source: 'stops',
+ minzoom: 14,
paint: {
- 'circle-color': '#000',
+ 'circle-color': [
+ 'case',
+ ['>', ['get', 'location_type'], 1],
+ colors.stopNode,
+ colors.stop
+ ],
'circle-radius': 4,
- 'circle-opacity': 0.75
- }
+ 'circle-opacity': 0.75,
+ 'circle-stroke-width': [
+ 'case',
+ ['==', ['get', 'location_type'], 2],
+ 2,
+ ['==', ['get', 'location_type'], 3],
+ 2,
+ ['==', ['get', 'location_type'], 4],
+ 2,
+ 0
+ ],
+ 'circle-stroke-color': [
+ 'case',
+ ['==', ['get', 'location_type'], 2],
+ colors.stopEntrance,
+ ['==', ['get', 'location_type'], 3],
+ colors.stopGeneric,
+ ['==', ['get', 'location_type'], 4],
+ colors.stopBoarding,
+ '#ffffff'
+ ]
+ },
+ filter: [
+ 'any',
+ ['==', ['get', 'location_type'], LocationTypes.STOP],
+ ['==', ['get', 'location_type'], LocationTypes.STATION],
+ ['==', ['get', 'location_type'], LocationTypes.ENTRANCE]
+ ]
}
]
@@ -203,4 +295,64 @@ const routeLayers = [
}
]
-export default { headways, colors, stopLayers, routeLayers }
+// Add these new layer definitions
+const otherLayers = {
+ polygons: {
+ id: 'polygons',
+ type: 'fill',
+ source: 'polygons',
+ paint: {
+ 'fill-color': '#ccc',
+ 'fill-opacity': 0.2
+ }
+ },
+ polygonsOutline: {
+ id: 'polygons-outline',
+ type: 'line',
+ source: 'polygons',
+ paint: {
+ 'line-width': 2,
+ 'line-color': '#000',
+ 'line-opacity': 0.2
+ }
+ },
+ points: {
+ id: 'points',
+ type: 'circle',
+ source: 'points',
+ paint: {
+ 'circle-color': ['coalesce', ['get', 'marker-color'], '#f03b20'],
+ 'circle-radius': ['coalesce', ['get', 'marker-radius'], 1],
+ 'circle-opacity': 0.4
+ }
+ },
+ lines: {
+ id: 'lines',
+ type: 'line',
+ source: 'lines',
+ paint: {
+ 'line-color': ['coalesce', ['get', 'stroke'], '#000'],
+ 'line-width': ['coalesce', ['get', 'stroke-width'], 2],
+ 'line-opacity': 1.0
+ }
+ }
+}
+
+// Add route layer defaults
+const routeLayerDefaults = {
+ type: 'line',
+ layout: {
+ 'line-cap': 'round',
+ 'line-join': 'round'
+ }
+}
+
+export default {
+ headways,
+ colors,
+ stopLayers,
+ routeLayers,
+ otherLayers,
+ routeLayerDefaults,
+ LocationTypes
+}
diff --git a/src/runtime/components/map-options-modal.vue b/src/runtime/components/map-options-modal.vue
new file mode 100644
index 00000000..c00f9f5f
--- /dev/null
+++ b/src/runtime/components/map-options-modal.vue
@@ -0,0 +1,168 @@
+
+
+
+
+
+
+
+
+
+ {{ label }}
+
+
+
+ location_type = {{ type }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Stop-to-stop geometries
+
+
+
+
+
+
+
+ Problematic geometries with long segment lengths
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/runtime/components/map-route-list.vue b/src/runtime/components/map-route-list.vue
deleted file mode 100644
index 03c0bd4a..00000000
--- a/src/runtime/components/map-route-list.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-
-
-
-
-
diff --git a/src/runtime/components/map-route-stop-list.vue b/src/runtime/components/map-route-stop-list.vue
new file mode 100644
index 00000000..425f0c20
--- /dev/null
+++ b/src/runtime/components/map-route-stop-list.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
diff --git a/src/runtime/components/map-viewer.vue b/src/runtime/components/map-viewer.vue
index fd556e05..1d738ad9 100644
--- a/src/runtime/components/map-viewer.vue
+++ b/src/runtime/components/map-viewer.vue
@@ -30,13 +30,32 @@ export default {
hash: { type: Boolean, default: false },
features: {
type: Array, default () { return [] }
+ },
+ showStopTypes: {
+ type: Object,
+ default: () => ({
+ 0: true, // Stop/Platform
+ 1: true, // Station
+ 2: true, // Entrance
+ 3: false, // Node
+ 4: false // Boarding Area
+ })
+ },
+ enableHover: {
+ type: Boolean,
+ default: true
+ },
+ highlightedStopFeatureId: {
+ type: [String, Number],
+ default: null
}
},
data () {
return {
map: null,
marker: null,
- hovering: [],
+ hoveringRoutes: [],
+ hoveringStops: [],
markerLayer: null
}
},
@@ -68,10 +87,35 @@ export default {
zoom () {
this.map.jumpTo({ center: this.center, zoom: this.zoom })
},
- markers(v) {
+ markers (v) {
this.drawMarkers(v)
+ },
+ showStopTypes: {
+ handler() {
+ this.updateFilters()
+ },
+ deep: true
+ },
+ highlightedStopFeatureId: {
+ handler(newId) {
+ if (!this.map) return
+
+ // Wait for map and source to be ready
+ this.map.once('load', () => {
+ console.log('Setting highlight state for stop:', {
+ id: newId,
+ source: 'stops',
+ features: this.stopFeatures.map(f => f.id)
+ })
+ this.map.setFeatureState(
+ { source: 'stops', id: newId },
+ { hover: true }
+ )
+ })
+ }
}
},
+
mounted () {
if (this.features) {
nextTick(() => { this.initMap() })
@@ -147,38 +191,70 @@ export default {
})
},
updateFeatures () {
+ console.log('Updating features:', {
+ stopFeatures: this.stopFeatures.length,
+ routeFeatures: this.routeFeatures.length,
+ usingTiles: {
+ stops: !!this.stopTiles,
+ routes: !!this.routeTiles
+ }
+ })
const polygons = this.features.filter((s) => { return s.geometry.type === 'MultiPolygon' || s.geometry.type === 'Polygon' })
const points = this.features.filter((s) => { return s.geometry.type === 'Point' })
const lines = this.features.filter((s) => { return s.geometry.type === 'LineString' })
- // check if map is initialized... TODO: this could be improved to try again
+
+ // check if map is initialized...
const p = this.map.getSource('polygons')
if (!p) {
+ console.warn('Map not initialized yet')
return
}
+
this.map.getSource('polygons').setData({ type: 'FeatureCollection', features: polygons })
this.map.getSource('lines').setData({ type: 'FeatureCollection', features: lines })
this.map.getSource('points').setData({ type: 'FeatureCollection', features: points })
- // this.map.getSource('stops').setData({ type: 'FeatureCollection', features: this.stopFeatures })
- // this.map.getSource('routes').setData({ type: 'FeatureCollection', features: this.routeFeatures })
- this.fitFeatures()
- },
- updateFilters () {
- for (const v of mapLayers.stopLayers) {
- const f = (v.filter || []).slice()
- if (f.length === 0) {
- f.push('all')
- }
- // Hide all routes?
- if (this.hideTiles) {
- f.push(['==', 'route_id', ''])
- }
- if (f.length > 1) {
- this.map.setFilter(v.name, f)
- } else {
- this.map.setFilter(v.name, null)
+
+ // Only update if using GeoJSON sources
+ if (!this.stopTiles) {
+ console.log('Updating GeoJSON stops source:', {
+ featureCount: this.stopFeatures.length,
+ features: this.stopFeatures.map(f => ({
+ id: f.id,
+ type: f.type,
+ properties: f.properties
+ }))
+ })
+ this.map.getSource('stops').setData({
+ type: 'FeatureCollection',
+ features: this.stopFeatures
+ })
+
+ // Set highlight state after updating source
+ if (this.highlightedStopFeatureId) {
+ this.map.setFeatureState(
+ { source: 'stops', id: this.highlightedStopFeatureId },
+ { hover: true }
+ )
}
}
+ if (!this.routeTiles) {
+ console.log('Updating GeoJSON routes source:', {
+ featureCount: this.routeFeatures.length,
+ features: this.routeFeatures.map(f => ({
+ id: f.id,
+ type: f.type,
+ properties: f.properties
+ }))
+ })
+ this.map.getSource('routes').setData({
+ type: 'FeatureCollection',
+ features: this.routeFeatures
+ })
+ }
+ this.fitFeatures()
+ },
+ updateFilters () {
for (const v of mapLayers.routeLayers) {
const f = (v.filter || []).slice()
if (f.length === 0) {
@@ -204,8 +280,35 @@ export default {
this.map.setFilter(v.name, null)
}
}
+
+ // Update stop layer filters
+ for (const layer of mapLayers.stopLayers) {
+ let filter = ['any']
+
+ // Show stop types based on showStopTypes prop
+ const visibleTypes = Object.entries(this.showStopTypes)
+ .filter(([_, visible]) => visible)
+ .map(([type]) => Number(type))
+
+ for (const type of visibleTypes) {
+ filter.push(['==', ['get', 'location_type'], type])
+ }
+
+ if (this.hideTiles) {
+ filter.push(['==', 'stop_id', ''])
+ }
+
+ this.map.setFilter(layer.name, filter)
+ }
},
createSources () {
+ console.log('Creating sources:', {
+ routeTiles: this.routeTiles,
+ stopTiles: this.stopTiles,
+ routeFeatures: this.routeFeatures.length,
+ stopFeatures: this.stopFeatures.length
+ })
+
this.map.addSource('polygons', {
type: 'geojson',
data: { type: 'FeatureCollection', features: [] }
@@ -218,8 +321,10 @@ export default {
type: 'geojson',
data: { type: 'FeatureCollection', features: [] }
})
- // Add route/stop sources, with geojson features as fallbacks
+ // Add route/stop sources
+
if (this.routeTiles) {
+ console.log('Adding vector tile source for routes:', this.routeTiles)
this.map.addSource('routes', {
type: 'vector',
tiles: [this.routeTiles.url],
@@ -227,11 +332,16 @@ export default {
maxzoom: this.routeTiles.maxzoom || 14
})
} else {
+ console.log('Adding GeoJSON source for routes:', {
+ featureCount: this.routeFeatures.length,
+ firstFeature: this.routeFeatures[0]
+ })
this.map.addSource('routes', {
type: 'geojson',
data: { type: 'FeatureCollection', features: this.routeFeatures }
})
}
+
if (this.stopTiles) {
this.map.addSource('stops', {
type: 'vector',
@@ -247,59 +357,14 @@ export default {
}
},
createLayers () {
- // Other feature layers
- this.map.addLayer({
- id: 'polygons',
- type: 'fill',
- source: 'polygons',
- layout: {},
- paint: {
- 'fill-color': '#ccc',
- 'fill-opacity': 0.2
- }
- })
- this.map.addLayer({
- id: 'polygons-outline',
- type: 'line',
- source: 'polygons',
- layout: {},
- paint: {
- 'line-width': 2,
- 'line-color': '#000',
- 'line-opacity': 0.2
- }
- })
- this.map.addLayer({
- id: 'points',
- type: 'circle',
- source: 'points',
- paint: {
- 'circle-color': ['coalesce', ['get', 'marker-color'], this.circleColor],
- 'circle-radius': ['coalesce', ['get', 'marker-radius'], this.circleRadius],
- 'circle-opacity': 0.4
- }
- })
- this.map.addLayer({
- id: 'lines',
- type: 'line',
- source: 'lines',
- layout: {},
- paint: {
- 'line-color': ['coalesce', ['get', 'stroke'], '#000'],
- 'line-width': ['coalesce', ['get', 'stroke-width'], 2],
- 'line-opacity': 1.0
- }
- })
- // Route/Stop layers
+ console.log('Creating layers with paint properties:', mapLayers)
+
+ // Add route layers
for (const v of mapLayers.routeLayers) {
const layer = {
+ ...mapLayers.routeLayerDefaults,
id: v.name,
- type: 'line',
source: 'routes',
- layout: {
- 'line-cap': 'round',
- 'line-join': 'round'
- },
minzoom: v.minzoom || 0,
paint: v.paint
}
@@ -311,6 +376,8 @@ export default {
}
this.map.addLayer(layer)
}
+
+ // Add stop layers
for (const v of mapLayers.stopLayers) {
const layer = {
id: v.name,
@@ -326,15 +393,25 @@ export default {
}
this.map.addLayer(layer)
}
- // add labels last
+
+ // Add other feature layers
+ Object.values(mapLayers.otherLayers).forEach(layer => {
+ this.map.addLayer({
+ ...layer,
+ source: layer.id === 'polygons-outline' ? 'polygons' : layer.id
+ })
+ })
+
+ // Add labels last
for (const labelLayer of labels('protomaps-base', 'grayscale')) {
this.map.addLayer(labelLayer)
}
+
// Set initial show generated geometry
this.updateFilters()
},
- drawMarkers(markers) {
+ drawMarkers (markers) {
for (const m of this.markerLayer) {
m.remove()
}
@@ -391,29 +468,147 @@ export default {
this.$emit('mapMove', { zoom: this.map.getZoom(), bbox: this.map.getBounds().toArray() })
},
mapMouseMove (e) {
+ // Skip hover effects if disabled
+ if (!this.enableHover) {
+ return
+ }
+
const map = this.map
- const features = map.queryRenderedFeatures(e.point, { layers: ['route-active'] })
- map.getCanvas().style.cursor = 'pointer'
- for (const k of this.hovering) {
- map.setFeatureState(
- { source: 'routes', id: k, sourceLayer: this.routeTiles ? this.routeTiles.id : null },
- { hover: false }
- )
+ let routeFeatures = []
+ let stopFeatures = []
+
+ // Query features
+ const routeLayers = this.routeTiles ? ['route-active'] : mapLayers.routeLayers.map(layer => layer.name)
+ const stopLayer = this.stopTiles ? 'stop-active' : 'stops'
+
+ // Query all route layers
+ for (const layer of routeLayers) {
+ if (map.getLayer(layer)) {
+ try {
+ const features = map.queryRenderedFeatures(e.point, { layers: [layer] })
+ routeFeatures = routeFeatures.concat(features)
+ } catch (err) {
+ console.warn(`Error querying route layer ${layer}:`, err)
+ }
+ }
}
- this.hovering = []
- for (const v of features) {
- this.hovering.push(v.id)
- map.setFeatureState({ source: 'routes', id: v.id, sourceLayer: this.routeTiles ? this.routeTiles.id : null }, { hover: true })
+
+ // Query stop layer
+ if (map.getLayer(stopLayer)) {
+ try {
+ stopFeatures = map.queryRenderedFeatures(e.point, { layers: [stopLayer] })
+ } catch (err) {
+ console.warn('Error querying stop features:', err)
+ }
}
+
+ // Update cursor
+ map.getCanvas().style.cursor = stopFeatures.length || routeFeatures.length ? 'pointer' : ''
+
+ // Handle route hover states
+ for (const k of this.hoveringRoutes) {
+ for (const layer of routeLayers) {
+ if (map.getLayer(layer)) {
+ try {
+ map.setFeatureState(
+ { source: 'routes', sourceLayer: this.routeTiles ? 'routes' : undefined, id: k },
+ { hover: false }
+ )
+ } catch (err) {
+ console.warn(`Error removing hover state from route ${k} in layer ${layer}:`, err)
+ }
+ }
+ }
+ }
+ this.hoveringRoutes = []
+
+ // Set hover state for found routes
+ for (const v of routeFeatures) {
+ this.hoveringRoutes.push(v.id)
+ for (const layer of routeLayers) {
+ if (map.getLayer(layer)) {
+ try {
+ map.setFeatureState(
+ { source: 'routes', sourceLayer: this.routeTiles ? 'routes' : undefined, id: v.id },
+ { hover: true }
+ )
+ } catch (err) {
+ console.warn(`Error setting hover state for route ${v.id} in layer ${layer}:`, err)
+ }
+ }
+ }
+ }
+
+ // Handle stop hover states
+ for (const k of this.hoveringStops) {
+ if (map.getLayer(stopLayer)) {
+ try {
+ map.setFeatureState(
+ { source: 'stops', id: k, sourceLayer: this.stopTiles ? this.stopTiles.id : null },
+ { hover: false }
+ )
+ } catch (err) {
+ console.warn('Error removing stop hover state:', err)
+ }
+ }
+ }
+ this.hoveringStops = []
+
+ for (const v of stopFeatures) {
+ this.hoveringStops.push(v.id)
+ if (map.getLayer(stopLayer)) {
+ try {
+ map.setFeatureState(
+ { source: 'stops', id: v.id, sourceLayer: this.stopTiles ? this.stopTiles.id : null },
+ { hover: true }
+ )
+ } catch (err) {
+ console.warn('Error setting stop hover state:', err)
+ }
+ }
+ }
+
+ // Always process and emit features
const agencyFeatures = {}
- for (const v of features) {
+ const processFeature = (v) => {
+ try {
+ if (v.properties.route_id) {
+ // Handle route
const agencyId = v.properties.agency_name
- const routeId = v.properties.route_id
if (agencyFeatures[agencyId] == null) {
- agencyFeatures[agencyId] = {}
+ agencyFeatures[agencyId] = { routes: {}, stops: {} }
+ }
+ agencyFeatures[agencyId].routes[v.properties.route_id] = v.properties
+ } else {
+ // Handle stop
+ const agencies = JSON.parse(v.properties.agencies || '[]')
+
+ const agencyId = agencies[0]?.agency_name || 'undefined'
+ if (agencyFeatures[agencyId] == null) {
+ agencyFeatures[agencyId] = { routes: {}, stops: {} }
+ }
+
+ const stopData = {
+ id: v.properties.stop_id,
+ stop_id: v.properties.stop_id,
+ stop_name: v.properties.stop_name,
+ location_type: v.properties.location_type || 0,
+ onestop_id: v.properties.onestop_id,
+ feed_onestop_id: v.properties.feed_onestop_id,
+ feed_version_sha1: v.properties.feed_version_sha1,
+ agencies: v.properties.agencies
+ }
+
+ agencyFeatures[agencyId].stops[v.properties.stop_id] = stopData
+ }
+ } catch (err) {
+ console.warn('Error processing feature:', err, v)
}
- agencyFeatures[agencyId][routeId] = v.properties
}
+
+ routeFeatures.forEach(processFeature)
+ stopFeatures.forEach(processFeature)
+
this.$emit('setAgencyFeatures', agencyFeatures)
}
}
diff --git a/src/runtime/components/pages/map.vue b/src/runtime/components/pages/map.vue
index 41f3dea7..e1160d38 100644
--- a/src/runtime/components/pages/map.vue
+++ b/src/runtime/components/pages/map.vue
@@ -16,6 +16,7 @@
:auto-fit="false"
:hide-tiles="activeTab === DIRECTIONS_TAB"
map-class="tall"
+ :show-stop-types="showStopTypes"
@set-zoom="mapSetZoom"
@set-agency-features="routesSetAgencyFeatures"
@map-click="mapClick"
@@ -24,42 +25,40 @@
-
-
+
+
-
- Zoom in to select routes and to see stop points.
+ Zoom in to select routes and to see stop points.
-
- Select routes
+
+ Use your cursor to highlight routes.
+ Click or tap for more information.
+ Zoom in to see stop points.
-
+
+ Use your cursor to highlight routes and stops.
+ Click or tap for more information.
+
+
+ Learn more about Transitland v2 Vector Tiles
+
-
- Options
+
+ Map Options
-
-
-
+
-
-
+
+
+
This feature is in a limited beta.
Please see the Routing API docs
@@ -102,40 +102,94 @@
@reset="directionsReset"
@set-depart-at="directionsSetDepartAt"
/>
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
- Show stop-to-stop geometries
-
-
-
-
- Show problematic geometries
-
-
-
+
diff --git a/src/runtime/components/route-stop-select.vue b/src/runtime/components/route-stop-select.vue
new file mode 100644
index 00000000..fe4ebc51
--- /dev/null
+++ b/src/runtime/components/route-stop-select.vue
@@ -0,0 +1,138 @@
+
+
+
+
+ {{ agency }}
+
+
+
+ {{ Object.keys(features.routes || {}).length }} routes
+
+ ,
+ {{ Object.keys(features.stops || {}).length }} stops
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ stop.stop_name || 'Unnamed Stop' }}
+
+
+
+
+
+ {{ stop.stop_name || 'Unnamed Stop' }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/runtime/components/stop-icon.vue b/src/runtime/components/stop-icon.vue
new file mode 100644
index 00000000..438c3add
--- /dev/null
+++ b/src/runtime/components/stop-icon.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/runtime/constants/stop-colors.ts b/src/runtime/constants/stop-colors.ts
new file mode 100644
index 00000000..95982f86
--- /dev/null
+++ b/src/runtime/constants/stop-colors.ts
@@ -0,0 +1,7 @@
+export const stopColors = {
+ stop: '#000000', // Stop/Platform (type 0)
+ station: '#000000', // Station (type 1)
+ entrance: '#808080', // Entrance/Exit (type 2)
+ node: '#ff0000', // Generic Node (type 3)
+ boarding: '#00ff00' // Boarding Area (type 4)
+}
\ No newline at end of file
diff --git a/src/runtime/types/filters.d.ts b/src/runtime/types/filters.d.ts
new file mode 100644
index 00000000..5109b5c7
--- /dev/null
+++ b/src/runtime/types/filters.d.ts
@@ -0,0 +1,22 @@
+declare module '@vue/runtime-core' {
+ interface ComponentCustomProperties {
+ $filters: {
+ makeRouteLink: (
+ onestop_id: string | null,
+ feed_onestop_id: string | null,
+ feed_version_sha1: string | null,
+ route_id: string | null,
+ id: number | null,
+ linkVersion: boolean
+ ) => string;
+ makeStopLink: (
+ onestop_id: string | null,
+ feed_onestop_id: string | null,
+ feed_version_sha1: string | null,
+ stop_id: string | null,
+ id: number | null,
+ linkVersion: boolean
+ ) => string;
+ }
+ }
+}
\ No newline at end of file