Skip to content

Instantly share code, notes, and snippets.

@RobertSasak
Created September 28, 2018 05:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RobertSasak/dfdaa56e1596839b395963ca7319fad7 to your computer and use it in GitHub Desktop.
Save RobertSasak/dfdaa56e1596839b395963ca7319fad7 to your computer and use it in GitHub Desktop.
Mapbox react-native map scale
import React from 'react'
import PropTypes from 'prop-types'
import {
View,
Text,
} from 'native-base'
import { primaryColor } from '../../constants'
const styles = {
wrapper: {
position: 'absolute',
bottom: 20,
left: 90,
width: 200,
height: 30,
},
scale: {
height: 30,
},
ruler: {
height: 10,
borderLeftWidth: 1,
borderLeftColor: primaryColor,
borderTopWidth: 1,
borderTopColor: primaryColor,
borderRightWidth: 1,
borderRightColor: primaryColor,
},
label: {
position: 'absolute',
right: 5,
fontSize: 10,
color: primaryColor,
},
}
const metersPerPixel = (lat, zoom) => 156543.03392 * Math.cos(lat * Math.PI / 180) / Math.pow(2, zoom + 1)
function getRoundNum(num) {
const pow10 = Math.pow(10, (`${Math.floor(num)}`).length - 1)
let d = num / pow10
d = d >= 10 ? 10 :
d >= 5 ? 5 :
d >= 3 ? 3 :
d >= 2 ? 2 : 1
return pow10 * d
}
function getScale(maxWidth, maxDistance, unit) {
let distance = getRoundNum(maxDistance)
const ratio = distance / maxDistance
if (unit === 'm' && distance >= 1000) {
distance = distance / 1000
unit = 'km'
}
return {
width: maxWidth * ratio,
label: distance + unit,
}
}
function getRuler(latitude, zoom, unit, maxWidth) {
const maxMeters = metersPerPixel(latitude, zoom) * maxWidth
if (unit === 'imperial') {
const maxFeet = 3.2808 * maxMeters
if (maxFeet > 5280) {
const maxMiles = maxFeet / 5280
return getScale(maxWidth, maxMiles, 'mi')
} else {
return getScale(maxWidth, maxFeet, 'ft')
}
} else if (unit === 'nautical') {
const maxNauticals = maxMeters / 1852
return getScale(maxWidth, maxNauticals, 'nm')
} else {
return getScale(maxWidth, maxMeters, 'm')
}
}
export default function Scale(props) {
const {
isChanging,
latitude,
maxWidth,
unit,
visible,
zoom,
} = props
const {
width,
label,
} = getRuler(latitude, zoom, unit, maxWidth)
return visible &&
<View
pointerEvents="none"
style={styles.wrapper}
>
<View style={[styles.scale, { width, opacity: isChanging ? 0.2 : 1 }]}>
<View style={styles.ruler} />
<Text
allowFontScaling={false}
style={styles.label}
>
{label}
</Text>
</View>
</View>
}
Scale.defaultProps = {
isChanging: false,
maxWidth: 100,
unit: 'metric',
visible: true,
}
Scale.propTypes = {
isChanging: PropTypes.bool,
latitude: PropTypes.number.isRequired,
maxWidth: PropTypes.number,
unit: PropTypes.string,
visible: PropTypes.bool,
zoom: PropTypes.number.isRequired,
}
@ludvigeriksson
Copy link

Thank you for sharing this!

Why is zoom + 1 required in metersPerPixel? (Your calculation gives the correct result, I just would like help understanding where the +1 comes from)

@RobertSasak
Copy link
Author

I would assume that it is just a whether zoom is zero based or it starts from 1.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment