Skip to content

Commit

Permalink
mouse-over and select stop points in map
Browse files Browse the repository at this point in the history
  • Loading branch information
drewda committed Jan 21, 2025
1 parent b017086 commit 3b23e45
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/runtime/components/feed-version-map-viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
>
<strong>Select routes</strong>
<br>
Use your cursor to highlight routes
Use your cursor to highlight route lines or stop points. Click for details.
</tl-map-route-list>
</div>
</div>
Expand Down
33 changes: 32 additions & 1 deletion src/runtime/components/map-layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,43 @@ const colors = {
}

const stopLayers = [
// Add hover/active layer first
{
name: 'stop-active',
type: 'circle',
source: 'stops',
minzoom: 14, // Match the stopTiles minzoom
paint: {
'circle-radius': [
'case',
['boolean', ['feature-state', 'hover'], false],
8, // hovered size
6 // normal size
],
'circle-color': colors.stop,
'circle-opacity': [
'case',
['boolean', ['feature-state', 'hover'], false],
0.8, // hovered opacity
0.0 // normal opacity (invisible when not hovered)
],
'circle-stroke-width': [
'case',
['boolean', ['feature-state', 'hover'], false],
2, // hovered stroke width
0 // normal stroke width
],
'circle-stroke-color': '#ffffff'
}
},
// Regular stops layer
{
name: 'stops',
type: 'circle',
source: 'stops',
minzoom: 14,
paint: {
'circle-color': '#000',
'circle-color': colors.stop,
'circle-radius': 4,
'circle-opacity': 0.75
}
Expand Down
28 changes: 24 additions & 4 deletions src/runtime/components/map-route-list.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<template>
<div>
<div>
<div v-if="Object.keys(agencyFeatures).length > 0">
<tl-route-select :agency-features="agencyFeatures" :collapse="true" :link="true" />
<div v-if="hasFeatures">
<tl-route-select
:agency-features="agencyFeatures"
:collapse="true"
:link="true"
:link-version="linkVersion"
:show-stops="true"
/>
</div>
<div v-else>
<slot name="default" />
Expand All @@ -18,12 +24,17 @@
<div v-if="isComponentModalActive" class="modal-card">
<header class="modal-card-head">
<p class="modal-card-title">
Select Route
Select Route or Stop
</p>
<button type="button" class="delete" @click="close" />
</header>
<section class="modal-card-body">
<tl-route-select :agency-features="agencyFeatures" :link="true" :link-version="linkVersion" />
<tl-route-select
:agency-features="agencyFeatures"
:link="true"
:link-version="linkVersion"
:show-stops="true"
/>
</section>
</div>
</template>
Expand All @@ -39,6 +50,15 @@ export default {
isComponentModalActive: { type: Boolean, default: false },
agencyFeatures: { type: Object, default () { return {} } }
},
computed: {
hasFeatures() {
return Object.keys(this.agencyFeatures).some(agency => {
const features = this.agencyFeatures[agency]
return (features.routes && Object.keys(features.routes).length > 0) ||
(features.stops && Object.keys(features.stops).length > 0)
})
}
},
methods: {
close () {
this.$emit('close')
Expand Down
50 changes: 40 additions & 10 deletions src/runtime/components/map-viewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export default {
return {
map: null,
marker: null,
hovering: []
hoveringRoutes: [],
hoveringStops: []
}
},
watch: {
Expand Down Expand Up @@ -363,28 +364,57 @@ export default {
},
mapMouseMove (e) {
const map = this.map
const features = map.queryRenderedFeatures(e.point, { layers: ['route-active'] })
// Query both routes and stops
const routeFeatures = map.queryRenderedFeatures(e.point, { layers: ['route-active'] })
const stopFeatures = map.queryRenderedFeatures(e.point, { layers: ['stop-active'] })
map.getCanvas().style.cursor = 'pointer'
for (const k of this.hovering) {
// Handle route hover states
for (const k of this.hoveringRoutes) {
map.setFeatureState(
{ source: 'routes', id: k, sourceLayer: this.routeTiles ? this.routeTiles.id : null },
{ hover: false }
)
}
this.hovering = []
for (const v of features) {
this.hovering.push(v.id)
this.hoveringRoutes = []
for (const v of routeFeatures) {
this.hoveringRoutes.push(v.id)
map.setFeatureState({ source: 'routes', id: v.id, sourceLayer: this.routeTiles ? this.routeTiles.id : null }, { hover: true })
}
// Handle stop hover states
for (const k of this.hoveringStops) {
map.setFeatureState(
{ source: 'stops', id: k, sourceLayer: this.stopTiles ? this.stopTiles.id : null },
{ hover: false }
)
}
this.hoveringStops = []
for (const v of stopFeatures) {
this.hoveringStops.push(v.id)
map.setFeatureState({ source: 'stops', id: v.id, sourceLayer: this.stopTiles ? this.stopTiles.id : null }, { hover: true })
}
// Combine features for emission
const agencyFeatures = {}
for (const v of features) {
const processFeature = (v) => {
const agencyId = v.properties.agency_name
const routeId = v.properties.route_id
const featureId = v.properties.route_id || v.properties.stop_id
const featureType = v.properties.route_id ? 'route' : 'stop'
if (agencyFeatures[agencyId] == null) {
agencyFeatures[agencyId] = {}
agencyFeatures[agencyId] = { routes: {}, stops: {} }
}
if (featureType === 'route') {
agencyFeatures[agencyId].routes[featureId] = v.properties
} else {
agencyFeatures[agencyId].stops[featureId] = v.properties
}
agencyFeatures[agencyId][routeId] = v.properties
}
routeFeatures.forEach(processFeature)
stopFeatures.forEach(processFeature)
console.log(agencyFeatures)
this.$emit('setAgencyFeatures', agencyFeatures)
}
}
Expand Down
13 changes: 9 additions & 4 deletions src/runtime/components/pages/map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/> -->
<div v-if="currentZoom < 8">
<h6 class="title is-6">
Zoom in to select routes and to see stop points.
Zoom in to select route lines and to see stop points.
</h6>
</div>
<div v-else>
Expand All @@ -39,10 +39,10 @@
@close="isComponentModalActive = false"
>
<h6 class="title is-6">
Select routes
Select routes and stops
</h6>
<div>
Use your cursor to highlight routes and see their names here. <br>Click for more details.
Use your cursor to highlight routes lines or stop points. Click for details.
</div>
</tl-map-route-list>
</div>
Expand Down Expand Up @@ -197,7 +197,12 @@ export default {
} else {
this.setCoords(null)
}
if (Object.keys(this.agencyFeatures).length > 0) {
// Check if we have any routes or stops to show in the modal
const hasFeatures = Object.keys(this.agencyFeatures).some(agency => {
return Object.keys(this.agencyFeatures[agency].routes || {}).length > 0 ||
Object.keys(this.agencyFeatures[agency].stops || {}).length > 0
})
if (hasFeatures) {
this.isComponentModalActive = true
}
}
Expand Down
92 changes: 66 additions & 26 deletions src/runtime/components/route-select.vue
Original file line number Diff line number Diff line change
@@ -1,33 +1,60 @@
<template>
<div>
<div v-for="(routes,agency) in agencyFeatures" :key="agency">
<div v-for="(features, agency) in agencyFeatures" :key="agency">
<h6 class="title is-6">
{{ agency }}
</h6>
<template v-if="isCollapsed">
<div>{{ Object.keys(routes).length }} routes</div>
<div>
{{ Object.keys(features.routes || {}).length }} routes,
{{ Object.keys(features.stops || {}).length }} stops
</div>
</template>
<template v-else>
<div v-for="route in routes" :key="route.id">
<nuxt-link
v-if="link"
:to="$filters.makeRouteLink(route.onestop_id, route.feed_onestop_id, route.feed_version_sha1, route.route_id, route.id, linkVersion)"
>
<tl-route-icon
:key="route.id"
:route-type="route.route_type"
:route-short-name="route.route_short_name"
:route-long-name="route.route_long_name"
/>
</nuxt-link>
<template v-else>
<tl-route-icon
:key="route.id"
:route-type="route.route_type"
:route-short-name="route.route_short_name"
:route-long-name="route.route_long_name"
/>
</template>
<!-- Routes -->
<div v-if="Object.keys(features.routes || {}).length > 0">
<h6 class="subtitle is-6">Routes:</h6>
<div v-for="route in features.routes" :key="route.id">
<nuxt-link
v-if="link"
:to="$filters.makeRouteLink(route.onestop_id, route.feed_onestop_id, route.feed_version_sha1, route.route_id, route.id, linkVersion)"
>
<tl-route-icon
:key="route.id"
:route-type="route.route_type"
:route-short-name="route.route_short_name"
:route-long-name="route.route_long_name"
/>
</nuxt-link>
<template v-else>
<tl-route-icon
:key="route.id"
:route-type="route.route_type"
:route-short-name="route.route_short_name"
:route-long-name="route.route_long_name"
/>
</template>
</div>
</div>

<!-- Stops -->
<div v-if="Object.keys(features.stops || {}).length > 0">
<h6 class="subtitle is-6">Stops:</h6>
<div v-for="stop in features.stops" :key="stop.id">
<nuxt-link
v-if="link"
:to="$filters.makeStopLink(stop.onestop_id, stop.feed_onestop_id, stop.feed_version_sha1, stop.stop_id, stop.id, linkVersion)"
>
<div class="stop-item">
<i class="fas fa-bus-alt" /> {{ stop.stop_name }}
</div>
</nuxt-link>
<template v-else>
<div class="stop-item">
<i class="fas fa-bus-alt" /> {{ stop.stop_name }}
</div>
</template>
</div>
</div>
</template>
</div>
Expand All @@ -41,19 +68,32 @@ export default {
maxAgencyRows: { type: Number, default () { return 5 } },
collapse: { type: Boolean },
linkVersion: { type: Boolean, default: false },
showStops: { type: Boolean, default: true },
agencyFeatures: { type: Object, default () { return {} } }
},
computed: {
isCollapsed () {
return this.routeCount > this.maxAgencyRows && this.collapse
return this.totalFeatureCount > this.maxAgencyRows && this.collapse
},
routeCount () {
totalFeatureCount () {
let count = 0
for (const v of Object.values(this.agencyFeatures)) {
count = count + Object.keys(v).length
for (const agency of Object.values(this.agencyFeatures)) {
count += Object.keys(agency.routes || {}).length
count += Object.keys(agency.stops || {}).length
}
return count
}
}
}
</script>

<style scoped>
.stop-item {
padding: 4px 0;
color: #363636;
}
.subtitle {
margin-top: 1rem;
margin-bottom: 0.5rem;
}
</style>

0 comments on commit 3b23e45

Please sign in to comment.