diff --git a/dist/icons/at.svg b/dist/icons/agencies/at.svg
similarity index 100%
rename from dist/icons/at.svg
rename to dist/icons/agencies/at.svg
diff --git a/dist/icons/metlink.svg b/dist/icons/agencies/metlink.svg
similarity index 100%
rename from dist/icons/metlink.svg
rename to dist/icons/agencies/metlink.svg
diff --git a/dist/icons/normal/metro-canterbury.png b/dist/icons/agencies/metro-canterbury.png
similarity index 100%
rename from dist/icons/normal/metro-canterbury.png
rename to dist/icons/agencies/metro-canterbury.png
diff --git a/dist/icons/atlogo.png b/dist/icons/atlogo.png
deleted file mode 100644
index 0b79723e..00000000
Binary files a/dist/icons/atlogo.png and /dev/null differ
diff --git a/dist/icons/locate-2-fill.svg b/dist/icons/locate-2-fill.svg
new file mode 100644
index 00000000..7880b15a
--- /dev/null
+++ b/dist/icons/locate-2-fill.svg
@@ -0,0 +1,3 @@
+
diff --git a/dist/icons/normal/bus.png b/dist/icons/normal/bus.png
deleted file mode 100644
index 2a787dd5..00000000
Binary files a/dist/icons/normal/bus.png and /dev/null differ
diff --git a/dist/icons/normal/bus.svg b/dist/icons/normal/bus.svg
index 28906872..c71ef99e 100644
--- a/dist/icons/normal/bus.svg
+++ b/dist/icons/normal/bus.svg
@@ -1,36 +1 @@
-
+
diff --git a/dist/icons/normal/cablecar-fill.svg b/dist/icons/normal/cablecar-fill.svg
deleted file mode 100644
index eebd9668..00000000
--- a/dist/icons/normal/cablecar-fill.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/icons/normal/cablecar.png b/dist/icons/normal/cablecar.png
deleted file mode 100644
index fc03e972..00000000
Binary files a/dist/icons/normal/cablecar.png and /dev/null differ
diff --git a/dist/icons/normal/cablecar.svg b/dist/icons/normal/cablecar.svg
index cc1ee383..15259c5b 100644
--- a/dist/icons/normal/cablecar.svg
+++ b/dist/icons/normal/cablecar.svg
@@ -1,26 +1 @@
-
+
diff --git a/dist/icons/normal/default.svg b/dist/icons/normal/default.svg
index 72b1dc6b..b9d80779 100644
--- a/dist/icons/normal/default.svg
+++ b/dist/icons/normal/default.svg
@@ -1,28 +1 @@
-
+
diff --git a/dist/icons/normal/ferry-fill.svg b/dist/icons/normal/ferry-fill.svg
deleted file mode 100644
index d4cebab3..00000000
--- a/dist/icons/normal/ferry-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/icons/normal/ferry.png b/dist/icons/normal/ferry.png
deleted file mode 100644
index c2da57f7..00000000
Binary files a/dist/icons/normal/ferry.png and /dev/null differ
diff --git a/dist/icons/normal/ferry.svg b/dist/icons/normal/ferry.svg
index 10c3d853..cfbde0be 100644
--- a/dist/icons/normal/ferry.svg
+++ b/dist/icons/normal/ferry.svg
@@ -1,26 +1 @@
-
+
diff --git a/dist/icons/normal/funicular-fill.svg b/dist/icons/normal/funicular-fill.svg
deleted file mode 100644
index 7aeb0c96..00000000
--- a/dist/icons/normal/funicular-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/icons/normal/funicular.svg b/dist/icons/normal/funicular.svg
deleted file mode 100644
index 7aeb0c96..00000000
--- a/dist/icons/normal/funicular.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/icons/normal/parkingbuilding-fill.svg b/dist/icons/normal/parkingbuilding-fill.svg
deleted file mode 100644
index fe23911c..00000000
--- a/dist/icons/normal/parkingbuilding-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/icons/normal/parkingbuilding.svg b/dist/icons/normal/parkingbuilding.svg
index 5793d74b..2f5bbd93 100644
--- a/dist/icons/normal/parkingbuilding.svg
+++ b/dist/icons/normal/parkingbuilding.svg
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+
diff --git a/dist/icons/normal/telecabin-fill.svg b/dist/icons/normal/telecabin-fill.svg
deleted file mode 100644
index 120ac688..00000000
--- a/dist/icons/normal/telecabin-fill.svg
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
diff --git a/dist/icons/normal/telecabin.svg b/dist/icons/normal/telecabin.svg
deleted file mode 100644
index 120ac688..00000000
--- a/dist/icons/normal/telecabin.svg
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
diff --git a/dist/icons/normal/train-fill.svg b/dist/icons/normal/train-fill.svg
deleted file mode 100644
index 9805838c..00000000
--- a/dist/icons/normal/train-fill.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/dist/icons/normal/train.png b/dist/icons/normal/train.png
deleted file mode 100644
index 4e61f513..00000000
Binary files a/dist/icons/normal/train.png and /dev/null differ
diff --git a/dist/icons/normal/train.svg b/dist/icons/normal/train.svg
index 2f97b241..2b559b8c 100644
--- a/dist/icons/normal/train.svg
+++ b/dist/icons/normal/train.svg
@@ -1,26 +1 @@
-
+
diff --git a/dist/icons/normal/vehicle-bike.png b/dist/icons/normal/vehicle-bike.png
new file mode 100644
index 00000000..03e91ad2
Binary files /dev/null and b/dist/icons/normal/vehicle-bike.png differ
diff --git a/dist/icons/normal/vehicle-bus.png b/dist/icons/normal/vehicle-bus.png
new file mode 100644
index 00000000..474d6dab
Binary files /dev/null and b/dist/icons/normal/vehicle-bus.png differ
diff --git a/dist/icons/normal/vehicle-cablecar.png b/dist/icons/normal/vehicle-cablecar.png
new file mode 100644
index 00000000..183a9016
Binary files /dev/null and b/dist/icons/normal/vehicle-cablecar.png differ
diff --git a/dist/icons/normal/vehicle-ferry.png b/dist/icons/normal/vehicle-ferry.png
new file mode 100644
index 00000000..902716a6
Binary files /dev/null and b/dist/icons/normal/vehicle-ferry.png differ
diff --git a/dist/icons/normal/vehicle-parkingbuilding.png b/dist/icons/normal/vehicle-parkingbuilding.png
new file mode 100644
index 00000000..e02df293
Binary files /dev/null and b/dist/icons/normal/vehicle-parkingbuilding.png differ
diff --git a/dist/icons/normal/vehicle-train.png b/dist/icons/normal/vehicle-train.png
new file mode 100644
index 00000000..e8e161a0
Binary files /dev/null and b/dist/icons/normal/vehicle-train.png differ
diff --git a/dist/icons/parkingbuilding.svg b/dist/icons/parkingbuilding.svg
new file mode 100644
index 00000000..2f5bbd93
--- /dev/null
+++ b/dist/icons/parkingbuilding.svg
@@ -0,0 +1,4 @@
+
diff --git a/dist/icons/telecabin-fill.svg b/dist/icons/telecabin-fill.svg
deleted file mode 100644
index 120ac688..00000000
--- a/dist/icons/telecabin-fill.svg
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
diff --git a/dist/icons/telecabin.svg b/dist/icons/telecabin.svg
deleted file mode 100644
index 120ac688..00000000
--- a/dist/icons/telecabin.svg
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
diff --git a/js/data/StationData.js b/js/data/StationData.js
index 6f9d9bdd..ac2e4ef5 100644
--- a/js/data/StationData.js
+++ b/js/data/StationData.js
@@ -1,4 +1,5 @@
import { endpoint } from '../../local'
+import StationStore from '../stores/StationStore.js'
import { t } from '../stores/translationStore.js'
class StationData {
@@ -27,6 +28,9 @@ class StationData {
}
const data = await res.json()
+ // gross, shouldn't be here...
+ StationStore.stationCache[stopCode] = data
+
const stopId = data.stop_id
let name = data.stop_name
const routeType = data.route_type
diff --git a/js/helpers/icons/au-syd.js b/js/helpers/icons/au-syd.js
deleted file mode 100644
index ba17a9cf..00000000
--- a/js/helpers/icons/au-syd.js
+++ /dev/null
@@ -1,20 +0,0 @@
-export const auSydMetadata = {
- default: {
- size: {
- width: 25,
- height: 25,
- },
- },
- 'default-selection': {
- size: {
- width: 25,
- height: 25,
- },
- },
- train: {},
- bus: {},
- ferry: {},
- coach: {},
- lightrail: {},
- subway: {},
-}
diff --git a/js/helpers/icons/index.js b/js/helpers/icons/index.js
deleted file mode 100644
index 5cb0496c..00000000
--- a/js/helpers/icons/index.js
+++ /dev/null
@@ -1,113 +0,0 @@
-import { icon as Icon } from 'leaflet'
-import { normalMetadata } from './normal.js'
-import { auSydMetadata } from './au-syd.js'
-
-// this map is from the routeType enum to the filename
-// e.g if a stop had the route type 4, it would look for ferry.svg
-const routeTypeMap = new Map()
-routeTypeMap.set(0, 'lightrail')
-routeTypeMap.set(1, 'subway')
-routeTypeMap.set(2, 'train')
-routeTypeMap.set(3, 'bus')
-routeTypeMap.set(4, 'ferry')
-routeTypeMap.set(5, 'cablecar')
-routeTypeMap.set(6, 'gondola')
-routeTypeMap.set(7, 'funicular')
-routeTypeMap.set(-1, 'parkingbuilding')
-routeTypeMap.set(-2, 'bike')
-routeTypeMap.set(200, 'coach')
-routeTypeMap.set(400, 'train')
-routeTypeMap.set(401, 'subway')
-routeTypeMap.set(900, 'lightrail')
-
-const iconStyles = {
- normal: normalMetadata,
- 'au-syd': auSydMetadata,
-}
-
-class Iconhelper {
- getClassName(prefix, variant) {
- if (variant !== null && prefix === 'normal') {
- return 'currentSelectionIcon larger'
- }
- return ''
- }
-
- getFileName(prefix, routeType, variant) {
- const pathPrefix = `/icons/${prefix}/`
- const fileType = '.svg'
-
- // legacy used fill instead of selection
- if (prefix === 'normal' && variant === 'selection') {
- return `${pathPrefix}${routeType}-fill${fileType}`
- }
- return `${pathPrefix}${routeType}${variant ? `-${variant}` : ''}${fileType}`
- }
-
- getIcon(prefixRequest, routeTypeEnum, variant = null) {
- const icon = {}
-
- let prefix = 'normal'
- let routeType = this.getRouteType(routeTypeEnum)
- // use the icon if it is defined
- if (
- iconStyles[prefixRequest] !== undefined &&
- iconStyles[prefixRequest][routeType] !== undefined
- ) {
- prefix = prefixRequest
- }
- // fallback to the normal icons, if one exists
- if (iconStyles[prefix][routeType] === undefined) {
- routeType = 'default'
- }
-
- icon.iconUrl = this.getFileName(prefix, routeType, variant)
- icon.iconSize = this.getSize(prefix, routeTypeEnum, variant)
- icon.className = this.getClassName(prefix, variant)
- return new Icon(icon)
- }
-
- getSize(prefixRequest, routeTypeEnum, variant = null) {
- // handles the differences between routeTypes
- let routeType = this.getRouteType(routeTypeEnum)
- let defaultIcon = 'default'
- if (variant !== null) {
- routeType += '-selection'
- defaultIcon += '-selection'
- }
-
- // returns the size of the icon, or uses the default size if not defined
- const prefix =
- iconStyles[prefixRequest] === undefined ? 'normal' : prefixRequest
- let { size } = iconStyles[prefix][defaultIcon]
- if (
- iconStyles[prefix][routeType] !== undefined &&
- iconStyles[prefix][routeType].size !== undefined
- ) {
- ;({ size } = iconStyles[prefix][routeType])
- }
- return [size.width, size.height]
- }
-
- getRouteType(routeTypeEnum) {
- let routeTypeQuery = routeTypeEnum
- // all trains are trains
- if (routeTypeEnum >= 100 && routeTypeEnum <= 199) {
- routeTypeQuery = 2
- }
- // all coaches are coaches
- if (routeTypeEnum >= 200 && routeTypeEnum <= 299) {
- routeTypeQuery = 200
- }
- // all buses are buses
- if (routeTypeEnum >= 700 && routeTypeEnum <= 799) {
- routeTypeQuery = 3
- }
- if (routeTypeMap.get(routeTypeQuery) === undefined) {
- return 'default'
- }
- return routeTypeMap.get(routeTypeQuery)
- }
-}
-
-export default Iconhelper
diff --git a/js/helpers/icons/normal.js b/js/helpers/icons/normal.js
deleted file mode 100644
index 4ed75c63..00000000
--- a/js/helpers/icons/normal.js
+++ /dev/null
@@ -1,34 +0,0 @@
-// How this file works!
-//
-// If there is no matching override, the application will fallback
-// to the value specified in default.
-// Keys will have to exist though, otherwise the unknown icon is used.
-export const normalMetadata = {
- default: {
- size: {
- width: 28,
- height: 34,
- },
- },
- 'default-selection': {
- size: {
- width: 28,
- height: 28,
- },
- },
- train: {},
- bus: {
- size: {
- width: 26,
- height: 32,
- },
- },
- ferry: {},
- cablecar: {},
- parkingbuilding: {
- size: {
- width: 28,
- height: 28,
- },
- },
-}
diff --git a/js/stores/CurrentLocation.js b/js/stores/CurrentLocation.js
index 339f0d14..3641f6a8 100644
--- a/js/stores/CurrentLocation.js
+++ b/js/stores/CurrentLocation.js
@@ -1,71 +1,16 @@
-import { vars } from '../styles.js'
import Events from './Events'
import StationStore from './StationStore'
-import SettingsStore from './SettingsStore'
-
-const { desktopThreshold } = vars
class CurrentLocation extends Events {
// ability to subscribe to location updates
constructor(props) {
super(props)
- this.geoID = null // geoID has a watcg on the watching of position
this.state = {
position: [0, 0], // user's current position
- gpsPosition: [0, 0], // same same but different?
- accuracy: 0, // accuracy in (km?) of user's current position
- timestamp: 0, // what time location was found
- error: '', // if geolocation returned error
- hasGranted: false,
initialSet: window.location.pathname.split('/')[1] !== 's',
}
}
- async componentDidMount() {
- // this only works in chrome & firefox not safari whoops.
- // also, don't do it on desktop
- if ('permissions' in navigator && window.innerWidth <= desktopThreshold) {
- const e = await navigator.permissions.query({ name: 'geolocation' })
- if (e.state === 'granted') {
- this.state.hasGranted = true
- }
- }
- }
-
- startWatch(updateType = 'pinmove') {
- // use this on page load to start watching poistion
- this.geoID = navigator.geolocation.watchPosition(
- position => {
- // make the geoID watch the position
- this.state.hasGranted = true
- this.setCurrentPosition(position, updateType)
-
- // can only move the map once
- if (updateType === 'mapmove') {
- updateType = 'pinmove'
- }
-
- // stops watching geolocation after 20 seconds
- setTimeout(() => {
- this.stopWatch()
- }, 20000)
- },
- error => {
- this.state.error = error.message
- },
- {
- enableHighAccuracy: true,
- timeout: 5000,
- }
- )
- }
-
- stopWatch() {
- // releases watch on geoID
- navigator.geolocation.clearWatch(this.geoID)
- this.geoID = null
- }
-
setInitialPosition(lat, lng) {
if (this.state.initialSet === false) {
this.state.position = [lat, lng]
@@ -74,48 +19,10 @@ class CurrentLocation extends Events {
}
}
- setCurrentPosition(position, updateType = 'pinmove') {
- const coords = [position.coords.latitude, position.coords.longitude]
- this.state.position = coords
- this.state.gpsPosition = coords
- this.state.accuracy = position.coords.accuracy
- this.state.timestamp = position.timestamp
- this.trigger(updateType)
- requestAnimationFrame(() => {
- SettingsStore.state.lastLocation = coords
- SettingsStore.saveState()
- })
- }
-
- resetCurrentPosition(updateType = 'pinmove') {
- this.state.position[0] = this.state.gpsPosition[0] + Math.random() / 100000
- this.state.position[1] = this.state.gpsPosition[1] + Math.random() / 100000
- this.trigger(updateType)
- }
-
setCity(prefix, position) {
this.state.position = position
this.trigger('mapmove-silent')
StationStore.getCity(...this.state.position)
}
-
- currentLocationButton() {
- if (this.geoID === null) {
- this.startWatch('mapmove')
- return
- }
- if (this.state.error === '') {
- // if no error
- this.resetCurrentPosition('mapmove') // move position by a very small random amount
- } else if (this.state.error.toLowerCase() === 'timeout expired') {
- this.resetCurrentPosition('mapmove')
- navigator.geolocation.getCurrentPosition(position => {
- this.state.hasGranted = true
- this.setCurrentPosition(position, 'mapmove')
- })
- } else {
- alert(this.state.error)
- }
- }
}
export default new CurrentLocation()
diff --git a/js/stores/SettingsStore.js b/js/stores/SettingsStore.js
index 48864ecc..0ab00e8c 100644
--- a/js/stores/SettingsStore.js
+++ b/js/stores/SettingsStore.js
@@ -12,7 +12,7 @@ class SettingsStore {
this.state[attr] = preState[attr]
})
}
- localStorage.setItem('AppVersion', '2.4.6')
+ localStorage.setItem('AppVersion', '3.0.0')
}
getState() {
diff --git a/js/stores/StationStore.js b/js/stores/StationStore.js
index f6564914..d4bd26e6 100644
--- a/js/stores/StationStore.js
+++ b/js/stores/StationStore.js
@@ -2,13 +2,11 @@ import Events from './Events'
import local from '../../local.js'
import SettingsStore from './SettingsStore.js'
import { t } from './translationStore.js'
-import IconHelper from '../helpers/icons/index.js'
+import { getIconName } from '../views/maps/util.jsx'
class StationStore extends Events {
stationCache = {}
- iconHelper = new IconHelper()
-
constructor(props) {
super(props)
this.currentCity = {
@@ -131,7 +129,7 @@ class StationStore extends Events {
const dataCollection = await Promise.all(promises)
dataCollection.forEach((data, key) => {
const no = stopNumber.split('+')[key]
- const icon = this.iconHelper.getRouteType(data.route_type)
+ const icon = getIconName(region, data.route_type, 'SavedStations')
let description = t('savedStations.stop', {
number: `${no} / ${data.stop_name}`,
})
diff --git a/js/stores/UiStore.js b/js/stores/UiStore.js
index e2b80025..b01a7134 100644
--- a/js/stores/UiStore.js
+++ b/js/stores/UiStore.js
@@ -15,7 +15,7 @@ class UIStore extends Events {
// base map stuff
basemap: null,
- basemapType: 'leaflet',
+ basemapType: 'mapbox',
// root card positions
oldCardPosition: 'default',
diff --git a/js/views/lines/AllLines.jsx b/js/views/lines/AllLines.jsx
deleted file mode 100644
index ce148daa..00000000
--- a/js/views/lines/AllLines.jsx
+++ /dev/null
@@ -1,377 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { View, Text, StyleSheet } from 'react-native'
-import leaflet from 'leaflet'
-import { withRouter } from 'react-router'
-
-import local from '../../../local.js'
-import { vars } from '../../styles.js'
-import UiStore from '../../stores/UiStore.js'
-import Header from '../reusable/Header.jsx'
-import LinkedScroll from '../reusable/LinkedScroll.jsx'
-
-import Layer from '../maps/Layer.jsx'
-
-const Icon = leaflet.icon
-const icons = new Map([
- [
- 2,
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 3,
- Icon({
- iconUrl: '/icons/normal/bus-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 700,
- Icon({
- iconUrl: '/icons/normal/bus-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 900,
- Icon({
- iconUrl: '/icons/normal/cablecar-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 4,
- Icon({
- iconUrl: '/icons/normal/ferry-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 100,
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 106,
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 400,
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 401,
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
-])
-
-let styles = null
-
-class AllLines extends React.Component {
- static propTypes = {
- match: PropTypes.object.isRequired,
- }
-
- layer = new Layer()
-
- pointsLayer = new Layer()
-
- liveLayer = new Layer()
-
- state = {
- color: '#666',
- trains: false,
- buses: false,
- lightrail: false,
- ferries: false,
- }
-
- constructor(props) {
- super(props)
-
- if (
- UiStore.state.lastTransition !== 'backward' &&
- UiStore.state.cardPosition === 'max'
- ) {
- requestAnimationFrame(() => {
- UiStore.setCardPosition('default')
- })
- }
- }
-
- componentDidMount() {
- this.getPositionData()
-
- this.liveRefresh = setInterval(() => {
- this.getPositionData()
- }, 10000)
- }
-
- componentWillUnmount() {
- this.layer.hide(true, true)
- this.pointsLayer.hide()
- this.liveLayer.hide()
- this.layer.unmounted = true
- this.pointsLayer.unmounted = true
- this.liveLayer.unmounted = true
-
- clearInterval(this.liveRefresh)
- this.cancelCallbacks = true
- }
-
- async getRealtime() {
- const { buses, ferries, trains, lightrail } = this.state
- console.log(this.state)
- const res = await fetch(
- `${local.endpoint}/${this.props.match.params.region}/realtime/all?ferries=${ferries}&buses=${buses}&trains=${trains}&lightrail=${lightrail}`
- )
-
- const data = await res.json()
- if (res.status >= 400) {
- const error = new Error(data.message)
- error.response = data
- throw error
- }
- return data
- }
-
- getPositionData = async () => {
- console.log(this.state)
- let busPositions = null
- let trainPositions = null
- let lightRailPositions = null
- let ferryPostitions = null
- try {
- const data = await this.getRealtime()
- this.liveLayer.hide()
- this.liveLayer = new Layer()
- busPositions = {
- type: 'MultiPoint',
- coordinates: [],
- }
- trainPositions = {
- type: 'MultiPoint',
- coordinates: [],
- }
- lightRailPositions = {
- type: 'MultiPoint',
- coordinates: [],
- }
- ferryPostitions = {
- type: 'MultiPoint',
- coordinates: [],
- }
- data.forEach(trip => {
- if (trip.latitude !== undefined) {
- switch (trip.route_type) {
- case 3:
- case 700:
- case 712:
- busPositions.coordinates.push([trip.longitude, trip.latitude])
-
- break
- case 2:
- case 400:
- case 401:
- case 100:
- case 106:
- trainPositions.coordinates.push([trip.longitude, trip.latitude])
- break
- case 0:
- case 900:
- lightRailPositions.coordinates.push([
- trip.longitude,
- trip.latitude,
- ])
- break
- case 4:
- case 1000:
- ferryPostitions.coordinates.push([trip.longitude, trip.latitude])
- break
- default:
- break
- }
- }
- })
- const busIcon = icons.get(3)
- const trainIcon = icons.get(2)
- const lightRailIcon = icons.get(900)
- const ferryIcon = icons.get(4)
- const { buses, ferries, trains, lightrail } = this.state
- if (buses) {
- this.liveLayer.add('geojson', busPositions, {
- icon: busIcon,
- })
- }
- if (trains) {
- this.liveLayer.add('geojson', trainPositions, {
- icon: trainIcon,
- })
- }
- if (lightrail) {
- this.liveLayer.add('geojson', lightRailPositions, {
- icon: lightRailIcon,
- })
- }
- if (ferries) {
- this.liveLayer.add('geojson', ferryPostitions, {
- icon: ferryIcon,
- })
- }
-
- if (this.cancelCallbacks === true) return 'cancelled'
- this.liveLayer.show()
- return 'done'
- } catch (err) {
- console.log(err)
- // who cares about the error
- console.error('Could not load realtime.')
- }
- }
-
- trainsOn = () => {
- this.setState(state => ({ trains: !state.trains }))
- this.getPositionData()
- }
-
- ferriesOn = () => {
- this.setState(state => ({ ferries: !state.ferries }))
- this.getPositionData()
- }
-
- lightrailOn = () => {
- this.setState(state => ({ lightrail: !state.lightrail }))
- this.getPositionData()
- }
-
- busesOn = () => {
- this.setState(state => ({ buses: !state.buses }))
- this.getPositionData()
- }
-
- render() {
- const { match } = this.props
- const {
- error,
- errorMessage,
- loading,
- trains,
- ferries,
- lightrail,
- buses,
- } = this.state
-
- if (error) {
- return (
-
-
-
-
- We couldn't load the {match.params.route_short_name} line in{' '}
- {match.params.region}.
-
- {errorMessage}
-
-
- )
- }
-
- const inner = loading ? (
-
- ) : (
-
-
-
-
-
-
- )
-
- return (
-
-
- {inner}
-
- )
- }
-}
-
-styles = StyleSheet.create({
- wrapper: {
- flex: 1,
- },
- direction: {
- paddingTop: vars.padding,
- paddingLeft: vars.padding,
- paddingBottom: vars.padding * 0.5,
- fontWeight: '600',
- fontSize: vars.defaultFontSize,
- fontFamily: vars.defaultFontFamily,
- },
- linkWrapper: {
- padding: vars.padding,
- },
- error: {
- padding: vars.padding,
- },
- errorMessage: {
- fontSize: vars.defaultFontSize,
- fontFamily: vars.defaultFontFamily,
- },
-})
-export default withRouter(AllLines)
diff --git a/js/views/lines/Line.jsx b/js/views/lines/Line.jsx
index 9d7ee402..a88f38b0 100644
--- a/js/views/lines/Line.jsx
+++ b/js/views/lines/Line.jsx
@@ -1,7 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { View, StyleSheet } from 'react-native'
-import leaflet from 'leaflet'
import { withRouter } from 'react-router'
import queryString from 'query-string'
@@ -10,61 +9,15 @@ import UiStore from '../../stores/UiStore.js'
import Header from '../reusable/Header.jsx'
import LinkedScroll from '../reusable/LinkedScroll.jsx'
-import Layer from '../maps/Layer.jsx'
+import Layer from '../maps/MapboxLayer.jsx'
import LineData from '../../data/LineData.js'
import { LineStops } from './stops/LineStops.jsx'
import { LineTimetable } from './timetable/LineTimetable.jsx'
import { LineErrorBoundary } from './LineErrorBoundary.jsx'
-import IconHelper from '../../helpers/icons/index.js'
import TripInfo from './TripInfo.jsx'
import LineIcon from '../../../dist/icons/linepicker.svg'
-const Icon = leaflet.icon
-const icons = new Map([
- [
- 'train',
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 'subway',
- Icon({
- iconUrl: '/icons/normal/train-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 'bus',
- Icon({
- iconUrl: '/icons/normal/bus-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 'cablecar',
- Icon({
- iconUrl: '/icons/normal/cablecar-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
- [
- 4,
- 'ferry',
- Icon({
- iconUrl: '/icons/normal/ferry-fill.svg',
- iconSize: [24, 24],
- className: 'vehIcon',
- }),
- ],
-])
-
let styles = null
class Line extends React.Component {
@@ -72,9 +25,7 @@ class Line extends React.Component {
match: PropTypes.object.isRequired,
}
- liveLayer = new Layer()
-
- iconHelper = new IconHelper()
+ liveLayer = new Layer('live-vehicles')
tripStops = []
@@ -122,7 +73,7 @@ class Line extends React.Component {
}
componentWillUnmount() {
- this.liveLayer.hide()
+ this.liveLayer.hide(true, false)
this.liveLayer.unmounted = true
clearInterval(this.liveRefresh)
@@ -160,8 +111,8 @@ class Line extends React.Component {
let busPositions = null
try {
const data = await this.lineData.getRealtime()
- this.liveLayer.hide()
- this.liveLayer = new Layer()
+ this.liveLayer.hide(true, true)
+ this.liveLayer = new Layer('live-vehicles')
busPositions = {
type: 'MultiPoint',
coordinates: [],
@@ -183,11 +134,13 @@ class Line extends React.Component {
await this.dataResolved
const { lineMetadata } = this.state
if (lineMetadata.length === 0) return 'cancelled' // this if it the line can't loa
- const icon = icons.get(
- this.iconHelper.getRouteType(lineMetadata[0].route_type)
- )
this.liveLayer.add('geojson', busPositions, {
- icon,
+ orderBefore: 'route-points-popups',
+ typeExtension: 'VehicleMarker',
+ typeExtensionOptions: {
+ region: this.lineData.region,
+ route_type: lineMetadata[0].route_type,
+ },
})
if (this.cancelCallbacks === true) return 'cancelled'
this.liveLayer.show()
diff --git a/js/views/lines/lineCommon.jsx b/js/views/lines/lineCommon.jsx
index 7410ce9d..44ae4dce 100644
--- a/js/views/lines/lineCommon.jsx
+++ b/js/views/lines/lineCommon.jsx
@@ -1,11 +1,13 @@
+import mapboxgl from 'mapbox-gl'
+
import { t } from '../../stores/translationStore.js'
import UiStore from '../../stores/UiStore.js'
export const renderShape = (shape, layer, routeColor) => {
layer.add('geojson', shape, {
+ orderBefore: 'route-points',
color: routeColor,
className: 'metro-line',
- order: 'back',
})
layer.show(shape.bounds, true, false)
return shape
@@ -19,57 +21,52 @@ export const renderStops = (
routeShortName
) => {
const geojson = {
- type: 'MultiPoint',
- coordinates: [],
+ type: 'FeatureCollection',
+ features: stops.map(stop => ({
+ type: 'Feature',
+ properties: {
+ id: stop.stop_id,
+ name: stop.stop_name,
+ },
+ geometry: {
+ type: 'Point',
+ coordinates: [stop.stop_lon, stop.stop_lat],
+ },
+ })),
}
- const stopsMap = {}
- stops.forEach(stop => {
- geojson.coordinates.push([stop.stop_lon, stop.stop_lat])
- stopsMap[[stop.stop_lat, stop.stop_lon].join(',')] = stop
- })
pointsLayer.add('geojson', geojson, {
+ orderBefore: 'live-vehicles',
typeExtension: 'CircleMarker',
typeExtensionOptions: {
- className: 'metro-dot',
color: routeColor,
- radius: 4,
- },
- maxZoom: 5,
- })
- pointsLayer.add('geojson', geojson, {
- typeExtension: 'InvisibleMarker',
- typeExtensionOptions: {
- zIndexOffset: 30,
- popupContent: (lat, lng) => {
- const data = stopsMap[[lat, lng].join(',')]
- return (
- // it's not quite react
+ radius: 3,
+ popupContent: e => {
+ const coordinates = e.features[0].geometry.coordinates.slice()
+ const name = e.features[0].properties.name
+ const id = e.features[0].properties.id
+
+ const popup = new mapboxgl.Popup({
+ closeButton: false,
+ className: 'mapbox-stops-popup',
+ })
+ .setLngLat(coordinates)
+ .setHTML(
+ `
+ ${name}
+ ${t('vech_loc.stop', { number: id })}
+
`
-
- ${data.stop_name}
- ${t('vech_loc.stop', { number: data.stop_id })}
-
- `
- )
- },
- popupOpen: e => {
- const elem = e.popup.getElement()
- const { station } = elem.querySelector('[data-station]').dataset
- const baseUrl = `/s/${region}/${station}`
- const extendedUrl = `${baseUrl}/timetable/${routeShortName}-2`
+ )
- elem
- .querySelector('.leaflet-service-button')
- .addEventListener('click', () => {
- UiStore.safePush(baseUrl)
- })
- elem
- .querySelector('.leaflet-timetable-button')
- .addEventListener('click', () => {
- UiStore.safePush(extendedUrl)
- })
+ popup.on('open', e => {
+ document
+ .querySelector('.mapbox-stops-popup button')
+ .addEventListener('click', () => {
+ popup.remove()
+ UiStore.safePush(`/s/${region}/${id}`)
+ })
+ })
+ popup.addTo(UiStore.state.basemap)
},
},
})
diff --git a/js/views/lines/stops/LineStops.jsx b/js/views/lines/stops/LineStops.jsx
index 561acbb8..fd598b0d 100644
--- a/js/views/lines/stops/LineStops.jsx
+++ b/js/views/lines/stops/LineStops.jsx
@@ -3,7 +3,7 @@ import { View, Text, StyleSheet } from 'react-native'
import { vars } from '../../../styles.js'
import SettingsStore from '../../../stores/SettingsStore.js'
-import Layer from '../../maps/Layer.jsx'
+import Layer from '../../maps/MapboxLayer.jsx'
import Spinner from '../../reusable/Spinner.jsx'
import { renderShape, renderStops } from '../lineCommon.jsx'
import LineData from '../../../data/LineData.js'
@@ -17,7 +17,7 @@ export class LineStops extends Component {
const { region } = this.props
this.lineData = new LineData({ region })
- this.pointsLayer = new Layer()
+ this.pointsLayer = new Layer('route-points')
this.shapesLayer = null
this.interpolatedShape = null
this.isShapeLoaded = false
@@ -25,9 +25,12 @@ export class LineStops extends Component {
this.state = {
loading: true,
}
+
+ this.mounted = false
}
componentDidMount() {
+ this.mounted = true
this.getStops()
}
@@ -40,14 +43,13 @@ export class LineStops extends Component {
}
componentWillUnmount() {
+ this.mounted = false
const { pointsLayer, shapesLayer } = this
// shapeslayer is nullable
if (shapesLayer) {
shapesLayer.hide(true, true)
- shapesLayer.unmounted = true
}
pointsLayer.hide()
- pointsLayer.unmounted = true
}
getStops = async () => {
@@ -57,6 +59,7 @@ export class LineStops extends Component {
this.lineData.trip_id = tripId
const { current, next, routeInfo } = await this.lineData.getTripStops()
+ if (!this.mounted) return
this.setState({
current,
next,
@@ -64,6 +67,7 @@ export class LineStops extends Component {
loading: false,
})
+ pointsLayer.hide()
renderStops(
current,
pointsLayer,
@@ -91,6 +95,7 @@ export class LineStops extends Component {
this.shapesLayer.hide(true, true)
this.shapesLayer = new Layer()
}
+ if (!this.mounted) return
return renderShape(shape, this.shapesLayer, color)
})
.catch(() => {
@@ -106,6 +111,7 @@ export class LineStops extends Component {
type: 'LineString',
coordinates: stops.map(stop => [stop.stop_lon, stop.stop_lat]),
}
+ if (!this.mounted) return
this.interpolatedShape = renderShape(
{ ...shape, ...this.lineData.getShapeBounds(shape) },
shapesLayer,
diff --git a/js/views/maps/BaseMap.jsx b/js/views/maps/BaseMap.jsx
deleted file mode 100644
index ac08f318..00000000
--- a/js/views/maps/BaseMap.jsx
+++ /dev/null
@@ -1,504 +0,0 @@
-import React from 'react'
-import PropTypes from 'prop-types'
-import { StyleSheet, TouchableOpacity } from 'react-native'
-import leaflet from 'leaflet'
-import * as reactLeaflet from 'react-leaflet'
-
-import { withRouter } from 'react-router'
-
-import local from '../../../local'
-
-import { vars } from '../../styles.js'
-import CurrentLocation from '../../stores/CurrentLocation.js'
-import StationStore from '../../stores/StationStore.js'
-import SettingsStore from '../../stores/SettingsStore.js'
-import UiStore from '../../stores/UiStore.js'
-import { t } from '../../stores/translationStore.js'
-
-import LocateIcon from '../../../dist/icons/locate-2.svg'
-
-import IconHelper from '../../helpers/icons/index.js'
-
-const Icon = leaflet.icon
-const { CRS, point, latLng } = leaflet
-const LeafletMap = reactLeaflet.Map
-const { Marker, TileLayer, ZoomControl, Circle, CircleMarker } = reactLeaflet
-
-const getDist = function(zoom) {
- let screensize = document.body.offsetWidth
- if (document.body.offsetHeight > screensize) {
- screensize = document.body.offsetHeight
- }
- let dist = Math.ceil(0.2 * screensize)
- if (zoom === 17) {
- dist = Math.ceil(0.45 * screensize)
- } else if (zoom === 16) {
- dist = Math.ceil(0.8 * screensize)
- }
- // max the api will handle is 1250
- if (dist > 1250) {
- dist = 1250
- }
- return dist
-}
-
-const getMarker = function(iconType, name) {
- if (iconType === 'bus') {
- name = name
- .trim()
- .replace(/\)/g, '')
- .replace(/\(/g, '')
- if (name.substring(3, 4) === ' ' || name.length === 3) {
- name = name.substring(0, 3)
- } else if (name.substring(2, 3) === ' ' || name.length === 2) {
- name = name.substring(0, 2)
- } else {
- name = name.substring(0, 1)
- }
- name = name.replace(/ /g, '').toUpperCase()
- const dynamic = `
-
- `
- return Icon({
- iconUrl: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(
- dynamic
- )}`,
- iconSize: [25, 41],
- })
- }
-}
-
-const calculateSnap = (stop, collisionMap) => {
- const markerWidth = 28
- const markerHeight = 34
-
- const xSnap = Math.floor(stop.x / markerWidth / 1)
- const ySnap = Math.floor(stop.y / markerHeight / 1)
-
- // checks itself, plus the 8 positions around it
- const foundColision = [
- (collisionMap[xSnap] || {})[ySnap],
- (collisionMap[xSnap + 1] || {})[ySnap],
- (collisionMap[xSnap + 1] || {})[ySnap + 1],
- (collisionMap[xSnap] || {})[ySnap + 1],
- (collisionMap[xSnap - 1] || {})[ySnap + 1],
- (collisionMap[xSnap - 1] || {})[ySnap],
- (collisionMap[xSnap - 1] || {})[ySnap - 1],
- (collisionMap[xSnap] || {})[ySnap - 1],
- (collisionMap[xSnap + 1] || {})[ySnap - 1],
- ].filter(f => f !== undefined)[0]
-
- collisionMap[xSnap] = collisionMap[xSnap] || {}
- if (
- collisionMap[xSnap][ySnap] === undefined &&
- collisionMap[xSnap][ySnap] === undefined
- ) {
- collisionMap[xSnap][ySnap] = stop
- }
-
- if (foundColision !== undefined) {
- const xDelta = foundColision.x - stop.x
- const yDelta = foundColision.y - stop.y
-
- if (Math.abs(xDelta) > Math.abs(yDelta) || xDelta === yDelta) {
- if (Math.abs(xDelta) < markerWidth) {
- if (xDelta < 0) {
- stop.x = stop.x + xDelta + markerWidth + 1
- } else {
- stop.x = stop.x + xDelta - markerWidth - 1
- }
- }
- } else if (Math.abs(yDelta) < markerHeight) {
- if (yDelta < 0) {
- stop.y = stop.y + yDelta + markerHeight + 1
- } else {
- stop.y = stop.y + yDelta - markerHeight - 1
- }
- }
- const nxSnap = stop.x
- const nySnap = stop.y
- collisionMap[stop.x] = collisionMap[nxSnap] || {}
- if (collisionMap[nxSnap][nySnap] === undefined) {
- collisionMap[nxSnap][nySnap] = stop
- } else {
- // bail out
- }
- }
-}
-
-// If we stop binding this to the history, we can make this pure
-class BaseMap extends React.Component {
- static propTypes = {
- history: PropTypes.object.isRequired,
- }
-
- map = React.createRef()
-
- iconHelper = new IconHelper()
-
- myIcons = {}
-
- state = {
- stops: [],
- position: SettingsStore.getState().lastLocation,
- positionMarker: [0, 0],
- initialPosition: true,
- loadmap: true,
- hideStops: false,
- online: window.navigator.onLine,
- }
-
- zoom = 17
-
- position = [...SettingsStore.getState().lastLocation, getDist(this.zoom)]
-
- componentDidMount() {
- window.addEventListener('online', this.triggerRetry)
- window.addEventListener('offline', this.goOffline)
- CurrentLocation.bind('pinmove', this.pinmove)
- CurrentLocation.bind('mapmove', this.mapmove)
- CurrentLocation.bind('mapmove-silent', this.mapmovesilent)
- UiStore.bind('stop-visibility', this.stopVisibility)
- this.getData(this.position[0], this.position[1], this.position[2])
-
- UiStore.basemap = this.map.current.leafletElement
-
- if (CurrentLocation.state.hasGranted) {
- CurrentLocation.startWatch()
- }
- }
-
- componentWillUnmount() {
- window.removeEventListener('online', this.triggerRetry)
- window.removeEventListener('offline', this.goOffline)
- CurrentLocation.unbind('pinmove', this.pinmove)
- CurrentLocation.unbind('mapmove', this.mapmove)
- CurrentLocation.unbind('mapmove-silent', this.mapmovesilent)
- UiStore.unbind('stop-visibility', this.stopVisibility)
- CurrentLocation.stopWatch()
- }
-
- stopVisibility = state => {
- if (this.state.hideStops !== state) {
- this.setState({
- hideStops: state,
- })
- if (state === false && this.zoom > 15) {
- this.getData(this.position[0], this.position[1], this.position[2])
- }
- }
- }
-
- pinmove = () => {
- if (this.state.initialPosition) {
- this.mapmove()
- } else {
- this.setState({
- positionMarker: CurrentLocation.state.position.slice(),
- })
- }
- }
-
- mapmove = () => {
- this.zoom = 17
- const position = CurrentLocation.state.position.slice()
- position[0] += 0.00000001
- this.setState({
- position,
- positionMarker: CurrentLocation.state.position.slice(),
- initialPosition: false,
- })
- }
-
- mapmovesilent = () => {
- this.zoom = 17
- this.setState({
- position: CurrentLocation.state.position.slice(),
- initialPosition: false,
- })
- }
-
- async getData(lat, lon, dist) {
- this.position = [lat, lon, dist]
- try {
- const res = await fetch(
- `${local.endpoint}/auto/station/search?lat=${lat.toFixed(
- 4
- )}&lon=${lon.toFixed(4)}&distance=${dist}`
- )
- const data = await res.json()
- data.forEach(item => {
- StationStore.stationCache[item.stop_id] = item
- const key = [item.stop_region, item.route_type].join(':')
- if (this.myIcons[key] === undefined) {
- this.myIcons[key] = this.iconHelper.getIcon(
- item.stop_region,
- item.route_type
- )
- }
- })
- this.setState({
- stops: data,
- })
- } catch (error) {
- console.log(error)
- }
- }
-
- triggerCurrentLocation = () => {
- CurrentLocation.currentLocationButton()
- }
-
- viewServices = (station, region) => () => {
- const { history } = this.props
- const split = history.location.pathname.split('/')
- const currentStation = `/s/${region}/${station}`
- if (split[1] === 's' && split.length === 4) {
- history.replace(currentStation)
- } else {
- history.push(currentStation)
- }
- }
-
- moveEnd = e => {
- const zoom = e.target.getZoom()
- this.zoom = zoom
- const newPos = e.target.getCenter()
-
- if (
- this.state.stops.length > 0 &&
- this.state.stops[0].stop_region !== StationStore.currentCity.prefix
- ) {
- StationStore.getCity(newPos.lat, newPos.lng)
- }
- let dist = 0
- if (zoom < 16 || this.state.hideStops) {
- this.setState({
- stops: [],
- })
- return
- }
- dist = getDist(zoom)
- this.getData(newPos.lat, newPos.lng, dist)
- }
-
- triggerRetry = () => {
- this.setState({
- loadmap: false,
- online: window.navigator.onLine,
- })
- setTimeout(() => {
- this.setState({
- loadmap: true,
- })
- this.getData(this.position[0], this.position[1], 250)
- }, 50)
- }
-
- goOffline = () => {
- this.setState({
- online: false,
- })
- }
-
- render() {
- const { stops } = this.state
- const collisionMap = {}
- stops.forEach(stop => {
- const point = CRS.EPSG3857.latLngToPoint(
- latLng(stop.stop_lat, stop.stop_lon),
- this.zoom
- )
- stop.x = point.x
- stop.y = point.y
- calculateSnap(stop, collisionMap)
- })
- // changes stuff in place (a bit gross)
- Object.keys(collisionMap).forEach(a => {
- Object.keys(collisionMap[a]).forEach(b => {
- const stop = collisionMap[a][b]
- const coords = CRS.EPSG3857.pointToLatLng(
- point(stop.x, stop.y),
- this.zoom
- )
- stop.stop_lat = coords.lat
- stop.stop_lon = coords.lng
- return b
- })
- })
-
- let stationMarker = null
- const splitName = window.location.pathname.split('/')
- if (splitName.length === 4 && splitName[1][0] === 's') {
- const currentStation = splitName[3]
- const item = StationStore.stationCache[currentStation]
- if (item !== undefined) {
- const icon = this.iconHelper.getRouteType(item.route_type)
- const markericon = this.iconHelper.getIcon(
- item.stop_region,
- item.route_type,
- 'selection'
- )
- stationMarker = (
-
- )
- }
- }
-
- const positionMap = {}
-
- let bigCircle
- if (this.state.accuracy < 500) {
- bigCircle = (
-
- )
- }
-
- let offline = null
- if (!this.state.online) {
- offline = (
-
-
You are not connected to the internet.
-
-
- )
- }
-
- let mapview
- if (this.state.loadmap) {
- mapview = (
-
-
- OpenMapTiles | © OpenStreetMap'
- }
- />
- {this.state.hideStops
- ? null
- : this.state.stops.map(stop => {
- const icon = this.iconHelper.getRouteType(stop.route_type)
- let markericon = this.myIcons[
- [stop.stop_region, stop.route_type].join(':')
- ]
- if (icon === 'bus') {
- const stopSplit = stop.stop_name.split('Stop')
- const platformSplit = stop.stop_name.split('Platform')
- if (stopSplit.length > 1 && stopSplit[1].trim().length > 0) {
- markericon = getMarker('bus', stopSplit[1])
- } else if (
- platformSplit.length > 1 &&
- platformSplit[1].trim().length > 0
- ) {
- markericon = getMarker('bus', platformSplit[1])
- }
- }
-
- // jono's awesome collison detection
- // basically checks if something is already there
- let lng = stop.stop_lon
- if (typeof positionMap[stop.stop_lat] === 'undefined') {
- positionMap[stop.stop_lat] = [lng]
- } else if (positionMap[stop.stop_lat].indexOf(lng) !== -1) {
- lng += 0.0002
- } else {
- positionMap[stop.stop_lat].push(lng)
- }
- return (
-
- )
- })}
- {bigCircle}
-
- {stationMarker}
-
- )
- }
-
- return (
-
-
-
-
- {mapview}
- {offline}
-
- )
- }
-}
-const styles = StyleSheet.create({
- locate: {
- backgroundColor: 'rgba(255,255,255,0.9)',
- position: 'absolute',
- zIndex: 10,
- paddingTop: vars.padding * 0.875,
- paddingBottom: vars.padding * 0.875,
- paddingLeft: vars.padding * 0.375,
- paddingRight: vars.padding * 0.375,
- top: vars.padding * 0.75,
- right: vars.padding * 0.75,
- borderRadius: 5,
- boxShadow: '0 1px 2px rgba(0,0,0,0.1)',
- },
-})
-export default withRouter(BaseMap)
diff --git a/js/views/maps/Layer.jsx b/js/views/maps/Layer.jsx
deleted file mode 100644
index 7703784a..00000000
--- a/js/views/maps/Layer.jsx
+++ /dev/null
@@ -1,124 +0,0 @@
-import leaflet from 'leaflet'
-import { vars } from '../../styles.js'
-import UiStore from '../../stores/UiStore.js'
-
-const { desktopThreshold } = vars
-const GeoJSON = leaflet.geoJSON
-const DivIcon = leaflet.divIcon
-const Marker = leaflet.marker
-const CircleMarker = leaflet.circleMarker
-
-class Layer {
- features = []
-
- visible = false
-
- unmounted = false
-
- show(bounds = null, dispose = true, hideStops = true, maxZoom = -1) {
- if (this.unmounted) {
- return
- }
- if (bounds !== null) {
- const options = {}
- if (document.documentElement.clientWidth <= desktopThreshold) {
- options.paddingBottomRight = [0, 350]
- }
- UiStore.basemap.fitBounds(
- [[bounds.lat_min, bounds.lon_min], [bounds.lat_max, bounds.lon_max]],
- options
- )
- }
- if (this.visible === true) return
- if (maxZoom > -1 && dispose === true) {
- this.maxZoom = maxZoom
- UiStore.basemap.on('zoomend', this.toggleOnZoom)
- return this.toggleOnZoom()
- }
- this.visible = true
- this.features.forEach(feature => {
- feature.addTo(UiStore.basemap)
- })
- if (hideStops) {
- UiStore.stopVisibility(hideStops)
- }
- }
-
- hide(dispose = true, hideStops = false) {
- if (this.visible === false) return
- if (this.maxZoom > -1 && dispose === true) {
- UiStore.basemap.off('zoomend', this.toggleOnZoom)
- }
- this.visible = false
- this.features.forEach(feature => {
- feature.remove(UiStore.basemap)
- })
- if (!hideStops) {
- UiStore.stopVisibility(hideStops)
- }
- }
-
- toggleOnZoom = () => {
- // this is pretty primative. need some way of checking the spacing between items
- if (this.maxZoom <= UiStore.basemap.getZoom()) {
- this.show(null, false)
- } else {
- this.hide(null, false)
- }
- }
-
- add(type, data, props = {}) {
- let feature = null
- if (type === 'geojson') {
- if (props.typeExtension === 'CircleMarker') {
- props.pointToLayer = function(feature, latlng) {
- return CircleMarker(latlng, props.typeExtensionOptions)
- }
- } else if (props.typeExtension === 'InvisibleMarker') {
- const divIcon = DivIcon({
- iconSize: [48, 48],
- className: 'invisible-icon',
- })
- props.typeExtensionOptions.icon = divIcon
- if (props.typeExtensionOptions.popupContent) {
- props.pointToLayer = function(feature, latlng) {
- const marker = Marker(latlng, props.typeExtensionOptions).bindPopup(
- props.typeExtensionOptions.popupContent(latlng.lat, latlng.lng)
- )
- marker.addEventListener(
- 'popupopen',
- props.typeExtensionOptions.popupOpen
- )
- return marker
- }
- } else {
- props.pointToLayer = function(feature, latlng) {
- return Marker(latlng, props.typeExtensionOptions)
- }
- }
- } else if (props.icon) {
- props.pointToLayer = function(feature, latlng) {
- return Marker(latlng, { icon: props.icon })
- }
- }
- feature = GeoJSON(data, props)
- }
- if (feature !== null) {
- this.features.push(feature)
- if (this.visible === true) {
- feature.addTo(UiStore.basemap)
- }
- if (props.order === 'back') {
- feature.getLayers().forEach(i => {
- requestAnimationFrame(() => i.bringToBack())
- })
- } else if (props.order === 'front') {
- feature.getLayers().forEach(i => {
- requestAnimationFrame(() => i.bringToFront())
- })
- }
- }
- }
-}
-
-export default Layer
diff --git a/js/views/maps/Mapbox.jsx b/js/views/maps/Mapbox.jsx
new file mode 100644
index 00000000..7702c734
--- /dev/null
+++ b/js/views/maps/Mapbox.jsx
@@ -0,0 +1,192 @@
+import React, { Component } from 'react'
+import mapboxgl from 'mapbox-gl'
+import { withRouter } from 'react-router'
+
+import CurrentLocation from '../../stores/CurrentLocation.js'
+import SettingsStore from '../../stores/SettingsStore.js'
+import StationStore from '../../stores/StationStore.js'
+import UiStore from '../../stores/UiStore.js'
+import { vars } from '../../styles.js'
+
+import MapboxStops from './MapboxStops.jsx'
+import Layer from './MapboxLayer.jsx'
+
+const { desktopThreshold } = vars
+
+// this token can only be used for waka.app
+const token =
+ 'pk.eyJ1IjoiY29uc2luZG8iLCJhIjoiY2tqeXZoMmowMDFpdjJ1cW0zcm90azFtcSJ9.WJ-oKBZImcOI9DagpVHh-Q'
+mapboxgl.accessToken = token
+
+class MapboxMap extends Component {
+ zoom = 16.5
+
+ selectedStopLayer = new Layer('selected-stop')
+
+ selectedStopLayerVisible = false
+
+ constructor(props) {
+ super(props)
+ this.state = {
+ online: window.navigator.onLine,
+ }
+ }
+
+ componentDidMount() {
+ window.addEventListener('online', this.triggerRetry)
+ window.addEventListener('offline', this.triggerOffline)
+ CurrentLocation.bind('mapmove-silent', this.mapmovesilent)
+ UiStore.bind('station-data-available', this.stationDataAvailable)
+
+ this.map = new mapboxgl.Map({
+ container: this.mapContainer,
+ style: 'mapbox://styles/consindo/ckjtsal580mwi19o1qyebauwv',
+ center: SettingsStore.getState()
+ .lastLocation.slice()
+ .reverse(),
+ zoom: this.zoom,
+ })
+ UiStore.state.basemap = this.map
+ this.map.touchPitch.disable()
+ this.map.addControl(new mapboxgl.NavigationControl(), 'bottom-left')
+ this.map.addControl(
+ new mapboxgl.GeolocateControl({
+ positionOptions: {
+ enableHighAccuracy: true,
+ },
+ trackUserLocation: true,
+ })
+ )
+
+ // waits for both data & map to load before adding to map
+ this.loadImages()
+ this.stops = new MapboxStops(this.map, this.props.history)
+ }
+
+ componentDidUpdate() {
+ this.renderSelectedStation()
+ }
+
+ stationDataAvailable = () => {
+ this.renderSelectedStation()
+ }
+
+ loadImages = () => {
+ const images = [
+ 'vehicle-bus',
+ 'vehicle-train',
+ 'vehicle-ferry',
+ 'vehicle-cablecar',
+ 'vehicle-parkingbuilding',
+ ]
+ images.forEach(id => {
+ this.map.loadImage(`/icons/normal/${id}.png`, (err, image) => {
+ if (err) return console.error(err)
+ this.map.addImage(`normal-${id}`, image, { pixelRatio: 2 })
+ })
+ })
+ }
+
+ mapmovesilent = () => {
+ const position = CurrentLocation.state.position.slice().reverse()
+ this.map.jumpTo({
+ center: position,
+ zoom: 16.5,
+ })
+ this.renderSelectedStation()
+ }
+
+ triggerRetry = () => {
+ this.setState({
+ online: window.navigator.onLine,
+ })
+ this.map.jumpTo({
+ center: SettingsStore.getState()
+ .lastLocation.slice()
+ .reverse(),
+ zoom: 16.5,
+ })
+ this.renderSelectedStation()
+ }
+
+ triggerOffline = () => {
+ this.setState({
+ online: false,
+ })
+ }
+
+ renderSelectedStation() {
+ const { history } = this.props
+ const splitName = history.location.pathname.split('/')
+ if (splitName.length === 4 && splitName[1][0] === 's') {
+ const currentStation = splitName[3]
+ if (this.selectedStopLayerVisible === currentStation) return
+
+ const item = StationStore.stationCache[currentStation]
+ if (item === undefined) return
+ this.selectedStopLayerVisible = currentStation
+
+ const coordinates = [item.stop_lon, item.stop_lat]
+ const zoom = 17.25
+
+ // hacks...
+ this.stops.stopVisibility(false, false)
+ this.stops.loadStops(
+ {
+ lng: item.stop_lon,
+ lat: item.stop_lat,
+ },
+ zoom
+ )
+
+ this.map.flyTo({
+ center: coordinates,
+ zoom,
+ padding: {
+ bottom:
+ document.documentElement.clientWidth <= desktopThreshold ? 350 : 0,
+ },
+ })
+
+ this.selectedStopLayer.hide()
+ this.selectedStopLayer.add(
+ 'geojson',
+ {
+ type: 'Point',
+ coordinates,
+ },
+ {
+ typeExtension: 'VehicleMarker',
+ typeExtensionOptions: {
+ region: splitName[2],
+ route_type: item.route_type,
+ size: 1.2,
+ },
+ }
+ )
+ this.selectedStopLayer.show(null, true, false)
+ } else {
+ if (!this.selectedStopLayerVisible) return
+ this.selectedStopLayerVisible = false
+ this.selectedStopLayer.hide()
+ }
+ }
+
+ render() {
+ const { online } = this.state
+ return (
+
+
(this.mapContainer = el)} />
+ {!online ? (
+
+
You are not connected to the internet.
+
+
+ ) : null}
+
+ )
+ }
+}
+export default withRouter(MapboxMap)
diff --git a/js/views/maps/MapboxLayer.jsx b/js/views/maps/MapboxLayer.jsx
new file mode 100644
index 00000000..35472a9c
--- /dev/null
+++ b/js/views/maps/MapboxLayer.jsx
@@ -0,0 +1,171 @@
+import { getIconName } from './util.jsx'
+import UiStore from '../../stores/UiStore.js'
+import { vars } from '../../styles.js'
+
+const { desktopThreshold } = vars
+
+class MapboxLayer {
+ id = Math.random().toString()
+
+ visible = false
+
+ mounted = false
+
+ popupCallback = null
+
+ additionalLayers = []
+
+ constructor(id) {
+ if (id) {
+ this.id = id
+ }
+ }
+
+ add(type, data, props = {}) {
+ const map = UiStore.state.basemap
+ if (type === 'geojson') {
+ map.addSource(this.id, {
+ type: 'geojson',
+ data,
+ })
+ const layer = {
+ id: this.id,
+ type: 'symbol',
+ source: this.id,
+ layout: {
+ visibility: 'none',
+ },
+ }
+ if (data.type === 'LineString') {
+ layer.type = 'line'
+ layer.layout['line-join'] = 'round'
+ layer.layout['line-cap'] = 'round'
+ layer.paint = {
+ 'line-color': props.color,
+ 'line-width': 4,
+ }
+ } else if (props.typeExtension === 'CircleMarker') {
+ layer.type = 'circle'
+ layer.paint = {
+ 'circle-color': '#fff',
+ 'circle-stroke-width': 2,
+ 'circle-radius': props.typeExtensionOptions.radius,
+ 'circle-stroke-color': props.typeExtensionOptions.color,
+ }
+ } else if (props.typeExtension === 'VehicleMarker') {
+ layer.layout = {
+ 'icon-image': getIconName(
+ props.typeExtensionOptions.region,
+ props.typeExtensionOptions.route_type,
+ 'VehicleMarker'
+ ),
+ 'icon-ignore-placement': true,
+ 'icon-size': props.typeExtensionOptions.size || 1,
+ }
+ } else {
+ console.log('add', type, data, props)
+ }
+
+ if (props.orderBefore && map.getLayer(props.orderBefore) != null) {
+ map.addLayer(layer, props.orderBefore)
+ } else {
+ map.addLayer(layer)
+ }
+
+ if ((props.typeExtensionOptions || {}).popupContent) {
+ const id = `${this.id}-popups`
+ map.addLayer({
+ id,
+ type: 'circle',
+ source: this.id,
+ paint: {
+ 'circle-radius': 15,
+ 'circle-opacity': 0,
+ },
+ })
+ this.additionalLayers.push(id)
+
+ this.popupCallback = props.typeExtensionOptions.popupContent
+ map.on('click', id, this.popupCallback)
+ map.on('mouseenter', id, this.setPointer)
+ map.on('mouseleave', id, this.setBlank)
+ }
+
+ this.mounted = true
+ } else {
+ console.log('add not geojson')
+ }
+ }
+
+ setPointer() {
+ const map = UiStore.state.basemap
+ map.getCanvas().style.cursor = 'pointer'
+ }
+
+ setBlank() {
+ const map = UiStore.state.basemap
+ map.getCanvas().style.cursor = ''
+ }
+
+ show(bounds = null, dispose = true, hideStops = true) {
+ const map = UiStore.state.basemap
+ if (bounds !== null) {
+ const options = {
+ padding: {
+ top: 20,
+ bottom: 50,
+ left: 20,
+ right: 20,
+ },
+ }
+ if (document.documentElement.clientWidth <= desktopThreshold) {
+ options.padding.bottom = 350
+ }
+ map.fitBounds(
+ [
+ [bounds.lon_max, bounds.lat_max],
+ [bounds.lon_min, bounds.lat_min],
+ ],
+ options
+ )
+ }
+
+ if (this.visible === true) return
+ const allLayers = [this.id, ...this.additionalLayers]
+ allLayers.forEach(id => map.setLayoutProperty(id, 'visibility', 'visible'))
+
+ if (hideStops) {
+ UiStore.stopVisibility(hideStops)
+ }
+ }
+
+ hide(dispose = true, hideStops = false) {
+ if (!this.mounted) return
+ const map = UiStore.state.basemap
+
+ if (map.getLayer(this.id) === undefined) return
+
+ const allLayers = [this.id, ...this.additionalLayers]
+ if (dispose === true) {
+ allLayers.forEach(id => map.removeLayer(id))
+ map.removeSource(this.id)
+ } else {
+ allLayers.forEach(id => map.setLayoutProperty(id, 'visibility', 'none'))
+ }
+
+ // removes the popups, little bit hacky, but don't really like popups anyway
+ if (this.popupCallback != null) {
+ const id = `${this.id}-popups`
+ map.off('click', id, this.popupCallback)
+ map.on('mouseenter', id, this.setPointer)
+ map.on('mouseleave', id, this.setBlank)
+ this.popupCallback = null
+ }
+
+ this.visible = false
+ if (!hideStops) {
+ UiStore.stopVisibility(hideStops)
+ }
+ }
+}
+export default MapboxLayer
diff --git a/js/views/maps/MapboxStops.jsx b/js/views/maps/MapboxStops.jsx
new file mode 100644
index 00000000..ec2a92ce
--- /dev/null
+++ b/js/views/maps/MapboxStops.jsx
@@ -0,0 +1,167 @@
+import local from '../../../local'
+import SettingsStore from '../../stores/SettingsStore.js'
+import StationStore from '../../stores/StationStore.js'
+import UiStore from '../../stores/UiStore.js'
+
+import { getDist, getIconName } from './util.jsx'
+
+export default class MapboxStops {
+ map = null
+
+ history = null
+
+ hideStops = false
+
+ mapboxLoaded = false
+
+ position = [...SettingsStore.getState().lastLocation, 16.5]
+
+ constructor(map, history) {
+ this.map = map
+ this.history = history
+
+ UiStore.bind('stop-visibility', this.stopVisibility)
+
+ const dataLoad = this.getData(...this.position)
+ const mapLoad = new Promise((resolve, reject) => {
+ // this is here so it handles the initial load race condition
+ map.on('moveend', () => {
+ this.loadStops()
+ })
+ this.map.on('load', () => {
+ this.setupStops()
+ this.mapboxLoaded = true
+ resolve()
+ })
+ })
+ Promise.all([dataLoad, mapLoad]).then(data => {
+ if (!this.hideStops) {
+ this.map.getSource('stops').setData(data[0])
+ }
+ })
+ }
+
+ setupStops = () => {
+ const map = this.map
+ map.addSource('stops', {
+ type: 'geojson',
+ data: {
+ type: 'FeatureCollection',
+ features: [],
+ },
+ })
+ const layerData = {
+ id: 'stops',
+ type: 'symbol',
+ source: 'stops',
+ layout: {
+ 'icon-image': '{icon}',
+ 'icon-size': { type: 'identity', property: 'icon_size' },
+ 'icon-ignore-placement': true,
+ },
+ }
+ if (map.getLayer('selected-stop') != null) {
+ map.addLayer(layerData, 'selected-stop')
+ } else {
+ map.addLayer(layerData)
+ }
+ map.on('click', 'stops', e => {
+ const { stop_region, stop_id } = e.features[0].properties
+ this.viewServices(stop_region, stop_id)
+ })
+ map.on('mouseenter', 'stops', () => {
+ map.getCanvas().style.cursor = 'pointer'
+ })
+ map.on('mouseleave', 'stops', () => {
+ map.getCanvas().style.cursor = ''
+ })
+ }
+
+ stopVisibility = (state, load = true) => {
+ if (this.hideStops !== state) {
+ const map = this.map
+ this.hideStops = state
+
+ // hide the layer
+ if (!this.mapboxLoaded) return
+ if (state === true) {
+ map.setLayoutProperty('stops', 'visibility', 'none')
+ } else {
+ map.setLayoutProperty('stops', 'visibility', 'visible')
+ if (load) this.loadStops()
+ }
+ }
+ }
+
+ loadStops = async (centerOverride, zoomOverride) => {
+ const map = this.map
+ const center = centerOverride || map.getCenter()
+ const zoom = zoomOverride || map.getZoom()
+ if (this.hideStops) return
+ const data = await this.getData(center.lat, center.lng, zoom)
+ if (!this.hideStops && this.mapboxLoaded) {
+ this.map.getSource('stops').setData(data)
+ }
+ }
+
+ viewServices = (region, station) => {
+ const history = this.history
+ const split = history.location.pathname.split('/')
+ const currentStation = `/s/${region}/${station}`
+ if (split[1] === 's' && split.length === 4) {
+ history.replace(currentStation)
+ } else {
+ history.push(currentStation)
+ }
+ }
+
+ async getData(lat, lon, zoom) {
+ const dist = getDist(zoom)
+ this.position = [lat, lon, dist]
+
+ if (zoom <= 14) {
+ return {
+ type: 'FeatureCollection',
+ features: [],
+ }
+ }
+
+ try {
+ const res = await fetch(
+ `${local.endpoint}/auto/station/search?lat=${lat.toFixed(
+ 4
+ )}&lon=${lon.toFixed(4)}&distance=${dist}`
+ )
+ const data = await res.json()
+ const features = data.map(stop => {
+ StationStore.stationCache[stop.stop_id] = stop
+ return {
+ type: 'Feature',
+ properties: {
+ stop_region: stop.stop_region,
+ stop_id: stop.stop_id,
+ stop_name: stop.stop_name,
+ route_type: stop.route_type,
+ icon: getIconName(
+ stop.stop_region,
+ stop.route_type,
+ 'stops',
+ stop.stop_name
+ ),
+ icon_size: zoom >= 16 ? 1 : 0.75,
+ },
+ geometry: {
+ type: 'Point',
+ coordinates: [stop.stop_lon, stop.stop_lat],
+ },
+ }
+ })
+ return {
+ type: 'FeatureCollection',
+ features: features,
+ }
+ } catch (error) {
+ console.log(error)
+ }
+ }
+}
diff --git a/js/views/maps/index.jsx b/js/views/maps/index.jsx
deleted file mode 100644
index 5d50d65d..00000000
--- a/js/views/maps/index.jsx
+++ /dev/null
@@ -1,4 +0,0 @@
-import BaseMap from './BaseMap.jsx'
-// i was going to put something in this file. but maybe not yet.
-// maybe this will be a toggle between current leaflet and mapboxgljs when we put it in
-export default BaseMap
diff --git a/js/views/maps/util.jsx b/js/views/maps/util.jsx
new file mode 100644
index 00000000..bc40e5ed
--- /dev/null
+++ b/js/views/maps/util.jsx
@@ -0,0 +1,84 @@
+export const getDist = zoom => {
+ let screensize = document.body.offsetWidth
+ if (document.body.offsetHeight > screensize) {
+ screensize = document.body.offsetHeight
+ }
+ let dist = Math.ceil(0.2 * screensize)
+ if (zoom <= 17 && zoom > 16) {
+ dist = Math.ceil(0.45 * screensize)
+ } else if (zoom <= 16) {
+ dist = Math.ceil(0.8 * screensize)
+ }
+ // max the api will handle is 1250
+ if (dist > 1250) {
+ dist = 1250
+ }
+ return dist
+}
+
+export const getIconName = (region, routeType, context, stopName) => {
+ if (region === 'au-syd') {
+ // TODO!
+ return 'normal-default'
+ }
+
+ let prefix = 'normal-'
+ if (context === 'VehicleMarker') {
+ prefix = 'normal-vehicle-'
+ } else if (context === 'SavedStations') {
+ prefix = ''
+ }
+
+ // the standard icons
+ if (routeType === 2) {
+ return `${prefix}train`
+ }
+
+ if (routeType === 3) {
+ // dynamic bus icons
+ if (context !== 'VehicleMarker' && context !== 'SavedStations') {
+ const stopSplit = stopName.split('Stop')
+ const platformSplit = stopName.split('Platform')
+ let markericon = null
+ if (stopSplit.length > 1 && stopSplit[1].trim().length > 0) {
+ markericon = stopSplit[1]
+ } else if (
+ platformSplit.length > 1 &&
+ platformSplit[1].trim().length > 0
+ ) {
+ markericon = platformSplit[1]
+ }
+
+ if (markericon != null) {
+ let name = markericon
+ .trim()
+ .replace(/\)/g, '')
+ .replace(/\(/g, '')
+ if (name.substring(3, 4) === ' ' || name.length === 3) {
+ name = name.substring(0, 3)
+ } else if (name.substring(2, 3) === ' ' || name.length === 2) {
+ name = name.substring(0, 2)
+ } else {
+ name = name.substring(0, 1)
+ }
+ name = name.replace(/ /g, '').toUpperCase()
+
+ if (['A', 'B', 'C', 'D', 'E', 'F'].includes(name)) {
+ return `${prefix}bus-${name.toLowerCase()}`
+ }
+ }
+ }
+ return `${prefix}bus`
+ }
+ if (routeType === 4) {
+ return `${prefix}ferry`
+ }
+ if (routeType === 5) {
+ return `${prefix}cablecar`
+ }
+ if (routeType === -1) {
+ return `${prefix}parkingbuilding`
+ }
+
+ return `${prefix}default`
+}
diff --git a/js/views/root/Content.jsx b/js/views/root/Content.jsx
index 534ca111..63fbdfeb 100644
--- a/js/views/root/Content.jsx
+++ b/js/views/root/Content.jsx
@@ -88,7 +88,7 @@ class RootContent extends React.Component {
+ return iconMap[icon] ||
}
triggerTap = () => {
diff --git a/js/views/shell/Content.jsx b/js/views/shell/Content.jsx
index cf066b34..00f5359b 100644
--- a/js/views/shell/Content.jsx
+++ b/js/views/shell/Content.jsx
@@ -21,7 +21,6 @@ import Settings from '../pages/Settings.jsx'
import NoMatch from '../pages/NoMatch.jsx'
import Wrapper from './Wrapper.jsx'
-import AllLines from '../lines/AllLines.jsx'
const { desktopThreshold } = vars
@@ -101,7 +100,6 @@ class Content extends React.Component {
render={wrapFn(Save)}
/>
-
svg path,
-.leaflet-tile-container {
- pointer-events: none;
-}
-
-.leaflet-marker-icon.leaflet-interactive,
-.leaflet-image-layer.leaflet-interactive,
-.leaflet-pane > svg path.leaflet-interactive {
- pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
- pointer-events: auto;
-}
-
-/* visual tweaks */
-
-.leaflet-container {
- background: #ddd;
- outline: 0;
-}
-.leaflet-container a {
- color: $lightblue;
-}
-.leaflet-container a.leaflet-active {
- outline: 2px solid orange;
-}
-.leaflet-zoom-box {
- border: 2px dotted #38f;
- background: rgba(255, 255, 255, 0.5);
-}
-
-/* general toolbar styles */
-
-.leaflet-bar {
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
- border-radius: 3px;
-}
-.leaflet-bar a,
-.leaflet-bar a:hover {
- background-color: #fff;
- border-bottom: 1px solid #ccc;
- width: 26px;
- height: 26px;
- line-height: 26px;
- display: block;
- text-align: center;
- text-decoration: none;
- color: black;
-}
-.leaflet-bar a,
-.leaflet-control-layers-toggle {
- background-position: 50% 50%;
- background-repeat: no-repeat;
- display: block;
-}
-.leaflet-bar a:hover {
- background-color: #f4f4f4;
-}
-.leaflet-bar a:first-child {
- border-top-left-radius: 3px;
- border-top-right-radius: 3px;
-}
-.leaflet-bar a:last-child {
- border-bottom-left-radius: 3px;
- border-bottom-right-radius: 3px;
- 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;
-}
-
-/* zoom control */
-
-.leaflet-control-zoom-in,
-.leaflet-control-zoom-out {
- font: bold 18px 'Lucida Console', Monaco, monospace;
- text-indent: 1px;
-}
-.leaflet-control-zoom-out {
- font-size: 20px;
-}
-
-.leaflet-touch .leaflet-control-zoom-in {
- font-size: 22px;
-}
-.leaflet-touch .leaflet-control-zoom-out {
- font-size: 24px;
-}
-
-/* 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;
-// padding-right: 5px;
-// }
-// .leaflet-control-layers-selector {
-// margin-top: 2px;
-// position: relative;
-// top: 1px;
-// }
-// .leaflet-control-layers label {
-// display: block;
-// }
-// .leaflet-control-layers-separator {
-// height: 0;
-// border-top: 1px solid #ddd;
-// margin: 5px -10px 5px -6px;
-// }
-
-/* attribution and scale controls */
-.leaflet-container .leaflet-control-attribution {
- background: rgba(255, 255, 255, 0.9);
- margin-bottom: 0;
-
- @media (max-width: $desktop-threshold) {
- display: none;
- }
-}
-@media (min-width: $desktop-threshold + 1) {
- .leaflet-control-zoom.leaflet-bar.leaflet-control {
- margin-bottom: 25px;
- }
-}
-.leaflet-control-attribution,
-.leaflet-control-scale-line {
- padding: 0 5px;
- color: #333;
-}
-.leaflet-control-attribution a {
- text-decoration: none;
-}
-.leaflet-control-attribution a:hover {
- text-decoration: underline;
-}
-.leaflet-container .leaflet-control-attribution,
-.leaflet-container .leaflet-control-scale {
- font-size: 11px;
-}
-.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;
- font-size: 11px;
- white-space: nowrap;
- overflow: hidden;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-
- background: #fff;
- background: rgba(255, 255, 255, 0.5);
-}
-.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 {
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
- 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: 18px;
- line-height: 1.4;
-}
-.leaflet-popup-content p {
- margin: 18px 0;
-}
-.leaflet-popup-tip-container {
- width: 40px;
- height: 20px;
- position: absolute;
- left: 50%;
- margin-left: -20px;
- // overflow: hidden;
- pointer-events: none;
-}
-.leaflet-popup-tip {
- width: 17px;
- height: 17px;
- padding: 1px;
-
- margin: -11px auto 0;
-
- -webkit-transform: rotate(45deg);
- -moz-transform: rotate(45deg);
- -ms-transform: rotate(45deg);
- -o-transform: rotate(45deg);
- transform: rotate(45deg);
-}
-.leaflet-popup-content-wrapper,
-.leaflet-popup-tip {
- background: $atblue;
- color: #fff;
- // box-shadow: 0 3px 14px rgba(0,0,0,0.4);
-}
-.leaflet-container a.leaflet-popup-close-button {
- // position: absolute;
- // top: 0;
- // right: 0;
- // padding: 4px 4px 0 0;
- // border: none;
- // text-align: center;
- // width: 18px;
- // height: 14px;
- // font: 16px/14px Tahoma, Verdana, sans-serif;
- // color: #c3c3c3;
- // text-decoration: none;
- // font-weight: bold;
- // background: transparent;
- display: none;
-}
-.leaflet-container a.leaflet-popup-close-button:hover {
- color: #999;
-}
-.leaflet-popup-scrolled {
- overflow: auto;
- border-bottom: 1px solid #ddd;
- border-top: 1px solid #ddd;
-}
-/* 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-clickable {
- 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;
-}
-
-// AT REALTIME EXCLUSIVE CSS
-.leaflet-popup-content {
- -webkit-tap-highlight-color: transparent;
-
- span {
- display: block;
- width: 180px;
- text-align: center;
- font-size: 13px;
- }
- img {
- max-width: 56px;
- }
- h2 {
- line-height: 1.1;
- margin: 10px 0 5px;
- letter-spacing: -0.5px;
- }
- h3 {
- color: #ddd;
- font-weight: 600;
- text-transform: uppercase;
- font-size: 1em;
- letter-spacing: -0.5px;
- margin: 0 0 10px;
- }
- button {
- border: 0;
- color: #fff;
- width: 100%;
- background: $lightblue;
- box-sizing: border-box;
- padding: 7px 10px;
- font-size: 14px;
- letter-spacing: -0.5px;
- font-weight: 600;
- font-family: 'Open Sans', sans-serif;
- text-transform: uppercase;
- outline: 0;
-
- &:hover {
- background: darken($lightblue, 5);
- }
- &:active {
- background: darken($lightblue, 10);
- }
- }
-}
-
-.leaflet-marker-icon {
- color: transparent;
-}
diff --git a/scss/_mapbox.scss b/scss/_mapbox.scss
new file mode 100644
index 00000000..42ed2eb1
--- /dev/null
+++ b/scss/_mapbox.scss
@@ -0,0 +1 @@
+.mapboxgl-map{font:12px/20px Helvetica Neue,Arial,Helvetica,sans-serif;overflow:hidden;position:relative;-webkit-tap-highlight-color:rgba(0,0,0,0)}.mapboxgl-canvas{position:absolute;left:0;top:0}.mapboxgl-map:-webkit-full-screen{width:100%;height:100%}.mapboxgl-canary{background-color:salmon}.mapboxgl-canvas-container.mapboxgl-interactive,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass{cursor:grab;-webkit-user-select:none;user-select:none}.mapboxgl-canvas-container.mapboxgl-interactive.mapboxgl-track-pointer{cursor:pointer}.mapboxgl-canvas-container.mapboxgl-interactive:active,.mapboxgl-ctrl-group button.mapboxgl-ctrl-compass:active{cursor:grabbing}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate .mapboxgl-canvas{touch-action:pan-x pan-y}.mapboxgl-canvas-container.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:pinch-zoom}.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan,.mapboxgl-canvas-container.mapboxgl-touch-zoom-rotate.mapboxgl-touch-drag-pan .mapboxgl-canvas{touch-action:none}.mapboxgl-ctrl-bottom-left,.mapboxgl-ctrl-bottom-right,.mapboxgl-ctrl-top-left,.mapboxgl-ctrl-top-right{position:absolute;pointer-events:none;z-index:2}.mapboxgl-ctrl-top-left{top:0;left:0}.mapboxgl-ctrl-top-right{top:0;right:0}.mapboxgl-ctrl-bottom-left{bottom:0;left:0}.mapboxgl-ctrl-bottom-right{right:0;bottom:0}.mapboxgl-ctrl{clear:both;pointer-events:auto;transform:translate(0)}.mapboxgl-ctrl-top-left .mapboxgl-ctrl{margin:10px 0 0 10px;float:left}.mapboxgl-ctrl-top-right .mapboxgl-ctrl{margin:10px 10px 0 0;float:right}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl{margin:0 0 10px 10px;float:left}.mapboxgl-ctrl-bottom-right .mapboxgl-ctrl{margin:0 10px 10px 0;float:right}.mapboxgl-ctrl-group{border-radius:4px;background:#fff}.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px rgba(0,0,0,.1)}@media (-ms-high-contrast:active){.mapboxgl-ctrl-group:not(:empty){box-shadow:0 0 0 2px ButtonText}}.mapboxgl-ctrl-group button{width:29px;height:29px;display:block;padding:0;outline:none;border:0;box-sizing:border-box;background-color:transparent;cursor:pointer}.mapboxgl-ctrl-group button+button{border-top:1px solid #ddd}.mapboxgl-ctrl button .mapboxgl-ctrl-icon{display:block;width:100%;height:100%;background-repeat:no-repeat;background-position:50%}@media (-ms-high-contrast:active){.mapboxgl-ctrl-icon{background-color:transparent}.mapboxgl-ctrl-group button+button{border-top:1px solid ButtonText}}.mapboxgl-ctrl-attrib-button:focus,.mapboxgl-ctrl-group button:focus{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl button:disabled{cursor:not-allowed}.mapboxgl-ctrl button:disabled .mapboxgl-ctrl-icon{opacity:.25}.mapboxgl-ctrl button:not(:disabled):hover{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-group button:focus:focus-visible{box-shadow:0 0 2px 2px #0096ff}.mapboxgl-ctrl-group button:focus:not(:focus-visible){box-shadow:none}.mapboxgl-ctrl-group button:focus:first-child{border-radius:4px 4px 0 0}.mapboxgl-ctrl-group button:focus:last-child{border-radius:0 0 4px 4px}.mapboxgl-ctrl-group button:focus:only-child{border-radius:inherit}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%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 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%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.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%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 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%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.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-out .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%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 13h-9z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-zoom-in .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%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.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-fullscreen .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M24 16v5.5c0 1.75-.75 2.5-2.5 2.5H16v-1l3-1.5-4-5.5 1-1 5.5 4 1.5-3h1zM6 16l1.5 3 5.5-4 1 1-4 5.5 3 1.5v1H7.5C5.75 24 5 23.25 5 21.5V16h1zm7-11v1l-3 1.5 4 5.5-1 1-5.5-4L6 13H5V7.5C5 5.75 5.75 5 7.5 5H13zm11 2.5c0-1.75-.75-2.5-2.5-2.5H16v1l3 1.5-4 5.5 1 1 5.5-4 1.5 3h1V7.5z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-shrink .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M18.5 16c-1.75 0-2.5.75-2.5 2.5V24h1l1.5-3 5.5 4 1-1-4-5.5 3-1.5v-1h-5.5zM13 18.5c0-1.75-.75-2.5-2.5-2.5H5v1l3 1.5L4 24l1 1 5.5-4 1.5 3h1v-5.5zm3-8c0 1.75.75 2.5 2.5 2.5H24v-1l-3-1.5L25 5l-1-1-5.5 4L17 5h-1v5.5zM10.5 13c1.75 0 2.5-.75 2.5-2.5V5h-1l-1.5 3L5 4 4 5l4 5.5L5 12v1h5.5z'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23999'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-compass .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 29 29' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10.5 14l4-8 4 8h-8z'/%3E%3Cpath d='M10.5 16l4 8 4-8h-8z' fill='%23ccc'/%3E%3C/svg%3E")}}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23333'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23aaa'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting .mapboxgl-ctrl-icon{animation:mapboxgl-spin 2s linear infinite}@media (-ms-high-contrast:active){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23fff'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23999'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e58978'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%2333b5e5'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background-error .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23e54e33'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3C/svg%3E")}.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate:disabled .mapboxgl-ctrl-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='29' height='29' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill='%23666'%3E%3Cpath d='M10 4C9 4 9 5 9 5v.1A5 5 0 005.1 9H5s-1 0-1 1 1 1 1 1h.1A5 5 0 009 14.9v.1s0 1 1 1 1-1 1-1v-.1a5 5 0 003.9-3.9h.1s1 0 1-1-1-1-1-1h-.1A5 5 0 0011 5.1V5s0-1-1-1zm0 2.5a3.5 3.5 0 110 7 3.5 3.5 0 110-7z'/%3E%3Ccircle cx='10' cy='10' r='2'/%3E%3Cpath d='M14 5l1 1-9 9-1-1 9-9z' fill='red'/%3E%3C/svg%3E")}}@keyframes mapboxgl-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}a.mapboxgl-ctrl-logo{width:88px;height:23px;margin:0 0 -4px -4px;display:block;background-repeat:no-repeat;cursor:pointer;overflow:hidden;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg opacity='.3' stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg opacity='.9' fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}a.mapboxgl-ctrl-logo.mapboxgl-compact{width:23px}@media (-ms-high-contrast:active){a.mapboxgl-ctrl-logo{background-color:transparent;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23000' stroke-width='3'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cg fill='%23fff'%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/g%3E%3C/svg%3E")}}@media (-ms-high-contrast:black-on-white){a.mapboxgl-ctrl-logo{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='88' height='23' viewBox='0 0 88 23' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' fill-rule='evenodd'%3E%3Cdefs%3E%3Cpath id='a' d='M11.5 2.25c5.105 0 9.25 4.145 9.25 9.25s-4.145 9.25-9.25 9.25-9.25-4.145-9.25-9.25 4.145-9.25 9.25-9.25zM6.997 15.983c-.051-.338-.828-5.802 2.233-8.873a4.395 4.395 0 013.13-1.28c1.27 0 2.49.51 3.39 1.42.91.9 1.42 2.12 1.42 3.39 0 1.18-.449 2.301-1.28 3.13C12.72 16.93 7 16 7 16l-.003-.017zM15.3 10.5l-2 .8-.8 2-.8-2-2-.8 2-.8.8-2 .8 2 2 .8z'/%3E%3Cpath id='b' d='M50.63 8c.13 0 .23.1.23.23V9c.7-.76 1.7-1.18 2.73-1.18 2.17 0 3.95 1.85 3.95 4.17s-1.77 4.19-3.94 4.19c-1.04 0-2.03-.43-2.74-1.18v3.77c0 .13-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V8.23c0-.12.1-.23.23-.23h1.4zm-3.86.01c.01 0 .01 0 .01-.01.13 0 .22.1.22.22v7.55c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V15c-.7.76-1.69 1.19-2.73 1.19-2.17 0-3.94-1.87-3.94-4.19 0-2.32 1.77-4.19 3.94-4.19 1.03 0 2.02.43 2.73 1.18v-.75c0-.12.1-.23.23-.23h1.4zm26.375-.19a4.24 4.24 0 00-4.16 3.29c-.13.59-.13 1.19 0 1.77a4.233 4.233 0 004.17 3.3c2.35 0 4.26-1.87 4.26-4.19 0-2.32-1.9-4.17-4.27-4.17zM60.63 5c.13 0 .23.1.23.23v3.76c.7-.76 1.7-1.18 2.73-1.18 1.88 0 3.45 1.4 3.84 3.28.13.59.13 1.2 0 1.8-.39 1.88-1.96 3.29-3.84 3.29-1.03 0-2.02-.43-2.73-1.18v.77c0 .12-.1.23-.23.23h-1.4c-.13 0-.23-.1-.23-.23V5.23c0-.12.1-.23.23-.23h1.4zm-34 11h-1.4c-.13 0-.23-.11-.23-.23V8.22c.01-.13.1-.22.23-.22h1.4c.13 0 .22.11.23.22v.68c.5-.68 1.3-1.09 2.16-1.1h.03c1.09 0 2.09.6 2.6 1.55.45-.95 1.4-1.55 2.44-1.56 1.62 0 2.93 1.25 2.9 2.78l.03 5.2c0 .13-.1.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.8 0-1.46.7-1.59 1.62l.01 4.68c0 .13-.11.23-.23.23h-1.41c-.13 0-.23-.11-.23-.23v-4.59c0-.98-.74-1.71-1.62-1.71-.85 0-1.54.79-1.6 1.8v4.5c0 .13-.1.23-.23.23zm53.615 0h-1.61c-.04 0-.08-.01-.12-.03-.09-.06-.13-.19-.06-.28l2.43-3.71-2.39-3.65a.213.213 0 01-.03-.12c0-.12.09-.21.21-.21h1.61c.13 0 .24.06.3.17l1.41 2.37 1.4-2.37a.34.34 0 01.3-.17h1.6c.04 0 .08.01.12.03.09.06.13.19.06.28l-2.37 3.65 2.43 3.7c0 .05.01.09.01.13 0 .12-.09.21-.21.21h-1.61c-.13 0-.24-.06-.3-.17l-1.44-2.42-1.44 2.42a.34.34 0 01-.3.17zm-7.12-1.49c-1.33 0-2.42-1.12-2.42-2.51 0-1.39 1.08-2.52 2.42-2.52 1.33 0 2.42 1.12 2.42 2.51 0 1.39-1.08 2.51-2.42 2.52zm-19.865 0c-1.32 0-2.39-1.11-2.42-2.48v-.07c.02-1.38 1.09-2.49 2.4-2.49 1.32 0 2.41 1.12 2.41 2.51 0 1.39-1.07 2.52-2.39 2.53zm-8.11-2.48c-.01 1.37-1.09 2.47-2.41 2.47s-2.42-1.12-2.42-2.51c0-1.39 1.08-2.52 2.4-2.52 1.33 0 2.39 1.11 2.41 2.48l.02.08zm18.12 2.47c-1.32 0-2.39-1.11-2.41-2.48v-.06c.02-1.38 1.09-2.48 2.41-2.48s2.42 1.12 2.42 2.51c0 1.39-1.09 2.51-2.42 2.51z'/%3E%3C/defs%3E%3Cmask id='c'%3E%3Crect width='100%25' height='100%25' fill='%23fff'/%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/mask%3E%3Cg stroke='%23fff' stroke-width='3' fill='%23fff'%3E%3Ccircle mask='url(%23c)' cx='11.5' cy='11.5' r='9.25'/%3E%3Cuse xlink:href='%23b' mask='url(%23c)'/%3E%3C/g%3E%3Cuse xlink:href='%23a'/%3E%3Cuse xlink:href='%23b'/%3E%3C/svg%3E")}}.mapboxgl-ctrl.mapboxgl-ctrl-attrib{padding:0 5px;background-color:hsla(0,0%,100%,.5);margin:0}@media screen{.mapboxgl-ctrl-attrib.mapboxgl-compact{min-height:20px;padding:2px 24px 2px 0;margin:10px;position:relative;background-color:#fff;border-radius:12px}.mapboxgl-ctrl-attrib.mapboxgl-compact-show{padding:2px 28px 2px 8px;visibility:visible}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact-show,.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact-show{padding:2px 8px 2px 28px;border-radius:12px}.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-inner{display:none}.mapboxgl-ctrl-attrib-button{display:none;cursor:pointer;position:absolute;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E");background-color:hsla(0,0%,100%,.5);width:24px;height:24px;box-sizing:border-box;border-radius:12px;outline:none;top:0;right:0;border:0}.mapboxgl-ctrl-bottom-left .mapboxgl-ctrl-attrib-button,.mapboxgl-ctrl-top-left .mapboxgl-ctrl-attrib-button{left:0}.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-inner,.mapboxgl-ctrl-attrib.mapboxgl-compact .mapboxgl-ctrl-attrib-button{display:block}.mapboxgl-ctrl-attrib.mapboxgl-compact-show .mapboxgl-ctrl-attrib-button{background-color:rgba(0,0,0,.05)}.mapboxgl-ctrl-bottom-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;right:0}.mapboxgl-ctrl-top-right>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;right:0}.mapboxgl-ctrl-top-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{top:0;left:0}.mapboxgl-ctrl-bottom-left>.mapboxgl-ctrl-attrib.mapboxgl-compact:after{bottom:0;left:0}}@media screen and (-ms-high-contrast:active){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' fill='%23fff'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}@media screen and (-ms-high-contrast:black-on-white){.mapboxgl-ctrl-attrib.mapboxgl-compact:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='24' height='24' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd'%3E%3Cpath d='M4 10a6 6 0 1012 0 6 6 0 10-12 0m5-3a1 1 0 102 0 1 1 0 10-2 0m0 3a1 1 0 112 0v3a1 1 0 11-2 0'/%3E%3C/svg%3E")}}.mapboxgl-ctrl-attrib a{color:rgba(0,0,0,.75);text-decoration:none}.mapboxgl-ctrl-attrib a:hover{color:inherit;text-decoration:underline}.mapboxgl-ctrl-attrib .mapbox-improve-map{font-weight:700;margin-left:2px}.mapboxgl-attrib-empty{display:none}.mapboxgl-ctrl-scale{background-color:hsla(0,0%,100%,.75);font-size:10px;border:2px solid #333;border-top:#333;padding:0 5px;color:#333;box-sizing:border-box}.mapboxgl-popup{position:absolute;top:0;left:0;display:flex;will-change:transform;pointer-events:none}.mapboxgl-popup-anchor-top,.mapboxgl-popup-anchor-top-left,.mapboxgl-popup-anchor-top-right{flex-direction:column}.mapboxgl-popup-anchor-bottom,.mapboxgl-popup-anchor-bottom-left,.mapboxgl-popup-anchor-bottom-right{flex-direction:column-reverse}.mapboxgl-popup-anchor-left{flex-direction:row}.mapboxgl-popup-anchor-right{flex-direction:row-reverse}.mapboxgl-popup-tip{width:0;height:0;border:10px solid transparent;z-index:1}.mapboxgl-popup-anchor-top .mapboxgl-popup-tip{align-self:center;border-top:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip{align-self:flex-start;border-top:none;border-left:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip{align-self:flex-end;border-top:none;border-right:none;border-bottom-color:#fff}.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip{align-self:center;border-bottom:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip{align-self:flex-start;border-bottom:none;border-left:none;border-top-color:#fff}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip{align-self:flex-end;border-bottom:none;border-right:none;border-top-color:#fff}.mapboxgl-popup-anchor-left .mapboxgl-popup-tip{align-self:center;border-left:none;border-right-color:#fff}.mapboxgl-popup-anchor-right .mapboxgl-popup-tip{align-self:center;border-right:none;border-left-color:#fff}.mapboxgl-popup-close-button{position:absolute;right:0;top:0;border:0;border-radius:0 3px 0 0;cursor:pointer;background-color:transparent}.mapboxgl-popup-close-button:hover{background-color:rgba(0,0,0,.05)}.mapboxgl-popup-content{position:relative;background:#fff;border-radius:3px;box-shadow:0 1px 2px rgba(0,0,0,.1);padding:10px 10px 15px;pointer-events:auto}.mapboxgl-popup-anchor-top-left .mapboxgl-popup-content{border-top-left-radius:0}.mapboxgl-popup-anchor-top-right .mapboxgl-popup-content{border-top-right-radius:0}.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content{border-bottom-left-radius:0}.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content{border-bottom-right-radius:0}.mapboxgl-popup-track-pointer{display:none}.mapboxgl-popup-track-pointer *{pointer-events:none;user-select:none}.mapboxgl-map:hover .mapboxgl-popup-track-pointer{display:flex}.mapboxgl-map:active .mapboxgl-popup-track-pointer{display:none}.mapboxgl-marker{position:absolute;top:0;left:0;will-change:transform;opacity:1;transition:opacity .2s}.mapboxgl-marker-occluded{opacity:.2}.mapboxgl-user-location-dot,.mapboxgl-user-location-dot:before{background-color:#1da1f2;width:15px;height:15px;border-radius:50%}.mapboxgl-user-location-dot:before{content:"";position:absolute;animation:mapboxgl-user-location-dot-pulse 2s infinite}.mapboxgl-user-location-dot:after{border-radius:50%;border:2px solid #fff;content:"";height:19px;left:-2px;position:absolute;top:-2px;width:19px;box-sizing:border-box;box-shadow:0 0 3px rgba(0,0,0,.35)}@keyframes mapboxgl-user-location-dot-pulse{0%{transform:scale(1);opacity:1}70%{transform:scale(3);opacity:0}to{transform:scale(1);opacity:0}}.mapboxgl-user-location-dot-stale{background-color:#aaa}.mapboxgl-user-location-dot-stale:after{display:none}.mapboxgl-user-location-accuracy-circle{background-color:rgba(29,161,242,.2);width:1px;height:1px;border-radius:100%}.mapboxgl-crosshair,.mapboxgl-crosshair .mapboxgl-interactive,.mapboxgl-crosshair .mapboxgl-interactive:active{cursor:crosshair}.mapboxgl-boxzoom{position:absolute;top:0;left:0;width:0;height:0;background:#fff;border:2px dotted #202020;opacity:.5}@media print{.mapbox-improve-map{display:none}}
diff --git a/scss/_search.scss b/scss/_search.scss
index ec054362..234c8620 100644
--- a/scss/_search.scss
+++ b/scss/_search.scss
@@ -4,69 +4,9 @@
}
.search {
height: 100%;
-}
-.leaflet-marker-icon.currentSelectionIcon {
- background: $atblue;
- padding: 7px;
- border-radius: 50%;
- z-index: 10000 !important;
- margin-left: -19px !important;
- margin-top: -31px !important;
- box-shadow: 0 1px 2px rgba(#000, 0.25);
-
- &.larger {
- margin-left: -22px !important;
- margin-top: -32px !important;
- padding: 8px;
- }
-}
-.top-button {
- bottom: 82px;
-}
-.bottom-button {
- bottom: 14px;
-}
-.blue-button {
- background: $lightblue;
- &:hover {
- background: darken($lightblue, 5);
- }
- &:active {
- background: darken($lightblue, 10);
- }
-
- svg {
- fill: #fff;
- }
-}
-@media (pointer: coarse) {
- .leaflet-control-zoom {
- display: none;
- }
-}
-
-.bigCurrentLocationCircle {
- stroke: none;
- //opacity: 0.15;
- fill: lighten($lightblue, 6);
-}
-
-.smallCurrentLocationCircle {
- fill-opacity: 1;
- fill: $current-location;
- stroke: white;
- stroke-width: 2;
-}
-
-.leaflet-marker-icon {
- margin-top: -24px !important;
-}
-.leaflet-popup-content {
- svg {
- display: block;
- position: relative !important;
- margin: 0 auto;
+ @media (max-width: $desktop-threshold) {
+ height: calc(100% - 58px);
}
}
.offline-container {
@@ -74,7 +14,8 @@
box-sizing: border-box;
left: 0;
right: 0;
- bottom: 0;
+ top: 0;
+ z-index: 5;
background: rgba(#000, 0.85);
color: #fff;
text-align: center;
diff --git a/scss/maps.scss b/scss/maps.scss
index 0b2a26bc..5d7ac5f7 100644
--- a/scss/maps.scss
+++ b/scss/maps.scss
@@ -1,3 +1,113 @@
-@import "_vars";
+@import '_vars';
+@import '_mapbox';
-@import "_leaflet";
\ No newline at end of file
+.mapboxgl-map {
+ height: 100%;
+}
+
+// the zoom & locate controls on the map
+.mapboxgl-ctrl-group:not(:empty) {
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2), 0 -1px 0px rgba(0, 0, 0, 0.02);
+}
+
+// our custom icon for the geolocation button
+.mapboxgl-ctrl button.mapboxgl-ctrl-geolocate .mapboxgl-ctrl-icon,
+.mapboxgl-ctrl
+ button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-background
+ .mapboxgl-ctrl-icon {
+ background-image: url(/icons/locate-2.svg);
+}
+.mapboxgl-ctrl
+ button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-active
+ .mapboxgl-ctrl-icon {
+ background-image: url(/icons/locate-2-fill.svg);
+}
+.mapboxgl-ctrl
+ button.mapboxgl-ctrl-geolocate.mapboxgl-ctrl-geolocate-waiting
+ .mapboxgl-ctrl-icon {
+ animation: waka-mapbox-fade 0.75s ease infinite;
+}
+.mapboxgl-ctrl-group button.mapboxgl-ctrl-geolocate {
+ width: 36px;
+ height: 52px;
+}
+@keyframes waka-mapbox-fade {
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+// stops it from overlapping with the bottom bar
+@media (max-width: $desktop-threshold) {
+ .mapboxgl-ctrl-bottom-left,
+ .mapboxgl-ctrl-bottom-right {
+ bottom: 8px;
+ }
+}
+
+// stops popups
+.mapbox-stops-popup {
+ font-size: 13px;
+ font-family: 'Open Sans', sans-serif;
+
+ &.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip,
+ &.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip,
+ &.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
+ border-top-color: $atblue;
+ }
+ &.mapboxgl-popup-anchor-top .mapboxgl-popup-tip,
+ &.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip,
+ &.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
+ border-bottom-color: $atblue;
+ }
+ &.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
+ border-right-color: $atblue;
+ }
+ &.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
+ border-left-color: $atblue;
+ }
+ .mapboxgl-popup-content {
+ background: $atblue;
+ color: #fff;
+ text-align: center;
+ width: 180px;
+ padding: 18px;
+
+ h2 {
+ line-height: 1.1;
+ margin: 0 0 5px;
+ letter-spacing: -0.5px;
+ }
+ h3 {
+ color: #ddd;
+ font-weight: 600;
+ text-transform: uppercase;
+ font-size: 1em;
+ letter-spacing: -0.5px;
+ margin: 0 0 10px;
+ }
+ button {
+ border: 0;
+ color: #fff;
+ width: 100%;
+ background: $lightblue;
+ box-sizing: border-box;
+ padding: 7px 10px;
+ font-size: 14px;
+ letter-spacing: -0.5px;
+ font-weight: 600;
+ text-transform: uppercase;
+ outline: 0;
+
+ &:active {
+ opacity: 0.7;
+ }
+ }
+ }
+}