Skip to content

Commit

Permalink
feat: Add interactive map SCALE to all maps, near the zoom buttons
Browse files Browse the repository at this point in the history
fixes: #69
  • Loading branch information
billyc committed Dec 17, 2021
1 parent b1a5413 commit 8021fce
Show file tree
Hide file tree
Showing 5 changed files with 199 additions and 21 deletions.
158 changes: 158 additions & 0 deletions src/components/MapScale.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<template lang="pug">
.map-scale(v-show="showScale")

.meters(:style="{width: `${metric.pixels}px`}")
p {{metric.length}}&nbsp;{{metric.label}}

.feet(:style="{width: `${miles.pixels}px`}")
p {{miles.length}}&nbsp;{{miles.label}}

</template>

<script lang="ts">
import { Vue, Component, Watch, Prop } from 'vue-property-decorator'
import globalStore from '@/store'
@Component({ components: {}, props: {} })
export default class VueComponent extends Vue {
private globalState = globalStore.state
private showScale = false
private mounted() {
this.zoomChanged()
}
@Watch('globalState.viewState.zoom')
@Watch('globalState.viewState.pitch')
@Watch('globalState.viewState.latitude')
private zoomChanged() {
// hide scale if map is pitched forward
if (this.globalState.viewState.pitch > 15 || this.globalState.viewState.zoom < 5) {
this.showScale = false
return
}
this.showScale = true
// generate scale based on latitude and zoom
// https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Resolution_and_Scale
const metersPerPixelAtEquator = 156543.03
const latitude = (Math.PI / 180.0) * this.globalState.viewState.latitude
const zoomLevel = this.globalState.viewState.zoom
const metersPerPixel = (metersPerPixelAtEquator * Math.cos(latitude)) / 2.0 ** zoomLevel
const pixelsForOneKM = (window.devicePixelRatio * 1000) / metersPerPixel
this.calculateBestMeasurements(pixelsForOneKM)
}
private breakpointsMetric = [
{ pixels: 10000, factor: 0.01, length: 10, label: 'm' },
{ pixels: 5000, factor: 0.02, length: 20, label: 'm' },
{ pixels: 2000, factor: 0.05, length: 50, label: 'm' },
{ pixels: 1000, factor: 0.1, length: 100, label: 'm' },
{ pixels: 500, factor: 0.2, length: 200, label: 'm' },
{ pixels: 250, factor: 0.5, length: 500, label: 'm' },
{ pixels: 100, factor: 1, length: 1, label: 'km' },
{ pixels: 50, factor: 2, length: 2, label: 'km' },
{ pixels: 20, factor: 5, length: 5, label: 'km' },
{ pixels: 8, factor: 10, length: 10, label: 'km' },
{ pixels: 4, factor: 20, length: 20, label: 'km' },
{ pixels: 2, factor: 50, length: 50, label: 'km' },
{ pixels: 1, factor: 100, length: 100, label: 'km' },
]
private breakpointsMiles = [
{ pixels: 20000, factor: 0.003787878, length: 20, label: 'ft' },
{ pixels: 10000, factor: 0.00946969696, length: 50, label: 'ft' },
{ pixels: 5000, factor: 0.0189393939, length: 100, label: 'ft' },
{ pixels: 2500, factor: 0.04734848, length: 250, label: 'ft' },
{ pixels: 1000, factor: 0.09469696, length: 500, label: 'ft' },
{ pixels: 500, factor: 0.18939393, length: 1000, label: 'ft' },
{ pixels: 300, factor: 0.25, length: 0.25, label: 'mi' },
{ pixels: 180, factor: 0.5, length: 0.5, label: 'mi' },
{ pixels: 80, factor: 1, length: 1, label: 'mi' },
{ pixels: 40, factor: 2, length: 2, label: 'mi' },
{ pixels: 20, factor: 5, length: 5, label: 'mi' },
{ pixels: 8, factor: 10, length: 10, label: 'mi' },
{ pixels: 4, factor: 25, length: 25, label: 'mi' },
{ pixels: 1.5, factor: 50, length: 50, label: 'mi' },
]
private metric = { pixels: 100, length: 1000, label: 'm' }
private miles = { pixels: 100, length: 1000, label: 'mi' }
private calculateBestMeasurements(pixelsForOneKM: number) {
// Metric: use either km or meters
let scaleMetric = { pixels: pixelsForOneKM / 200, length: 5, label: 'm' }
for (let i = 0; i < this.breakpointsMetric.length; i++) {
const breakpoint = this.breakpointsMetric[i]
if (pixelsForOneKM > breakpoint.pixels) break
scaleMetric = {
pixels: pixelsForOneKM * breakpoint.factor,
length: breakpoint.length,
label: breakpoint.label,
}
}
// U.S.: use either feet or miles
const pixelsForOneMile = pixelsForOneKM * 1.609344
let scaleMiles = { pixels: (10 * pixelsForOneMile) / 5280, length: 10, label: 'ft' }
for (let i = 0; i < this.breakpointsMiles.length; i++) {
const breakpoint = this.breakpointsMiles[i]
if (pixelsForOneMile > breakpoint.pixels) break
scaleMiles = {
pixels: pixelsForOneMile * breakpoint.factor,
length: breakpoint.length,
label: breakpoint.label,
}
}
this.metric = scaleMetric
this.miles = scaleMiles
}
}
</script>

<style scoped lang="scss">
@import '@/styles.scss';
.map-scale {
display: flex;
flex-direction: column;
overflow-x: hidden;
pointer-events: none;
}
p {
margin: 0px 0px;
padding: 1px 4px;
font-size: 0.8rem;
color: var(--textBold);
opacity: 0.7;
text-align: right;
}
.meters {
margin-left: auto;
background-color: var(--scaleBg);
border-left: var(--scaleBorder);
border-right: var(--scaleBorder);
border-bottom: var(--scaleBorder);
}
.feet {
margin-left: auto;
margin-top: -1px;
background-color: var(--scaleBg);
border-left: var(--scaleBorder);
border-right: var(--scaleBorder);
border-top: var(--scaleBorder);
}
@media only screen and (max-width: 640px) {
}
</style>
27 changes: 21 additions & 6 deletions src/components/ZoomButtons.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<template lang="pug">
.zoom-button-area
.map-complications

map-scale.map-scale

.zoom-buttons
.button-single.button-top
img.img-button(@click="zoomIn()"
src="@/assets/images/sw_plus.jpg")
Expand All @@ -10,15 +14,17 @@
img.img-button(@click="setNorth()"
src="@/assets/images/sw_north_arrow.png"
:style="{transform: `rotate(${arrowRotation}deg)`}"
)
)


</template>

<script lang="ts">
import { Vue, Component, Watch, Prop } from 'vue-property-decorator'
import globalStore from '@/store'
import MapScale from '@/components/MapScale.vue'
@Component({ components: {}, props: {} })
@Component({ components: { MapScale }, props: {} })
export default class VueComponent extends Vue {
private zoomInFactor = 0.5
private zoomOutFactor = 0.5
Expand Down Expand Up @@ -85,16 +91,21 @@ export default class VueComponent extends Vue {
<style scoped lang="scss">
@import '@/styles.scss';
.zoom-button-area {
.map-complications {
position: absolute;
top: 8px;
right: 8px;
display: flex;
flex-direction: column;
pointer-events: auto;
flex-direction: row;
pointer-events: none;
cursor: pointer;
}
.zoom-buttons {
margin-left: auto;
pointer-events: auto;
}
.button-single:last-child {
margin-bottom: 0;
}
Expand Down Expand Up @@ -125,6 +136,10 @@ export default class VueComponent extends Vue {
height: 28px;
}
.map-scale {
margin: 0.25rem 0.5rem 0 0;
}
@media only screen and (max-width: 640px) {
}
</style>
22 changes: 10 additions & 12 deletions src/plugins/aggregate-od/AggregateOd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
.status-blob(v-show="!thumbnail && loadingText")
p {{ loadingText }}

zoom-buttons.zoom-buttons(v-if="!thumbnail")

.map-complications(v-if="!thumbnail && !isMobile()")
legend-box.complication(:rows="legendRows")
scale-box.complication(:rows="scaleRows")
Expand Down Expand Up @@ -82,6 +84,7 @@ import LineFilterSlider from './LineFilterSlider.vue'
import ScaleBox from './ScaleBoxOD.vue'
import TimeSlider from './TimeSlider.vue'
import ScaleSlider from '@/components/ScaleSlider.vue'
import ZoomButtons from '@/components/ZoomButtons.vue'
import {
MAP_STYLES,
Expand Down Expand Up @@ -124,6 +127,7 @@ const INPUTS = {
ScaleBox,
ScaleSlider,
TimeSlider,
ZoomButtons,
},
})
class MyComponent extends Vue {
Expand Down Expand Up @@ -415,16 +419,6 @@ class MyComponent extends Vue {
bearing: 0,
jump: true, // initial map
})
// if (this.thumbnail) {
// this.mymap.fitBounds(lnglat, {
// animate: false,
// })
// } else {
// this.mymap.fitBounds(lnglat, {
// padding,
// animate: false,
// })
// }
}
} catch (e) {
// no consequence if json was weird, just drop it
Expand All @@ -433,8 +427,6 @@ class MyComponent extends Vue {
this.mymap.on('click', this.handleEmptyClick)
// Start doing stuff AFTER the MapBox library has fully initialized
this.mymap.on('load', this.mapIsReady)
this.mymap.addControl(new maplibregl.NavigationControl(), 'top-right')
this.mymap.on('move', this.handleMapMotion)
// clean up display just when we're in thumbnail mode
Expand Down Expand Up @@ -1417,6 +1409,12 @@ h4 {
padding: 0.5rem 0.25rem 0.5rem 0.25rem;
}
.zoom-buttons {
grid-row: 1 / 3;
grid-column: 1 / 3;
margin: 0 0 auto auto;
}
@media only screen and (max-width: 640px) {
}
</style>
9 changes: 6 additions & 3 deletions src/plugins/transit-demand/TransitDemand.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
:style="{transform: 'translate(-50%,-50%) rotate('+stop.bearing+'deg)', left: stop.xy.x + 'px', top: stop.xy.y+'px'}"
)

zoom-buttons(v-if="!thumbnail")
drawing-tool(v-if="!thumbnail")

collapsible-panel.left-side(v-if="!thumbnail"
Expand Down Expand Up @@ -76,6 +77,7 @@ import NewXmlFetcher from '@/workers/NewXmlFetcher.worker?worker'
import TransitSupplyWorker from './TransitSupplyHelper.worker?worker'
import LegendBox from './LegendBox.vue'
import DrawingTool from '@/components/DrawingTool/DrawingTool.vue'
import ZoomButtons from '@/components/ZoomButtons.vue'
import {
FileSystem,
Expand All @@ -97,7 +99,10 @@ class Departure {
public routes: Set<string> = new Set()
}
@Component({ i18n, components: { CollapsiblePanel, LeftDataPanel, LegendBox, DrawingTool } })
@Component({
i18n,
components: { CollapsiblePanel, LeftDataPanel, LegendBox, DrawingTool, ZoomButtons },
})
class MyComponent extends Vue {
@Prop({ required: true })
private root!: string
Expand Down Expand Up @@ -461,8 +466,6 @@ class MyComponent extends Vue {
this.mymap.on('click', this.handleEmptyClick)
this.mymap.keyboard.disable() // so arrow keys don't pan
this.mymap.addControl(new maplibregl.NavigationControl(), 'top-right')
}
private handleClickedMetric(metric: { field: string }) {
Expand Down
4 changes: 4 additions & 0 deletions src/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ $dashboardWidth: 70rem;
--borderColor: #66666633;
--borderZoomButtons: 2px solid var(--borderColor);
--logoOpacity: opacity(40%);
--scaleBorder: 1px solid #7974a0;
--scaleBg: #ffffffaa;
}

.dark-mode {
Expand Down Expand Up @@ -90,4 +92,6 @@ $dashboardWidth: 70rem;
--borderColor: #00000088;
--borderZoomButtons: 1px solid var(--borderColor);
--logoOpacity: opacity(30%);
--scaleBorder: 1px solid #8fabca;
--scaleBg: #33333360;
}

0 comments on commit 8021fce

Please sign in to comment.