Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save dannythunder/e7ae3ac1a6ad4f2a297046edfadc14af to your computer and use it in GitHub Desktop.
Save dannythunder/e7ae3ac1a6ad4f2a297046edfadc14af to your computer and use it in GitHub Desktop.
Scriptable app script to find closest scooter in Copenhagen
let googleApiKey = '' // Insert your own key for Google Maps + Directions JS SDK here
let copenhagenBounds = {minLat: 55.5, minLong: 12.5, maxLat: 55.8, maxLong: 12.7}
let copenhagenCenter = {lat: copenhagenBounds.maxLat - copenhagenBounds.minLat, lng: copenhagenBounds.maxLong - copenhagenBounds.minLong}
let getLocation = Location.current()
// const getLocation = new Promise(resolve => resolve({latitude: 55.7, longitude: 12.5})) // hardcoded location resolver for airplane debug use
const [allTIERScooters, allVOIScooters, myLocation] = await Promise.all([getTIERScooters(copenhagenBounds), getVOIScooters(copenhagenBounds), getLocation])
console.log('My location: ' + JSON.stringify(myLocation))
if (allTIERScooters.length + allVOIScooters.length <= 0) {
let message = 'No scooters available right now'
console.log(message)
if (config.runsWithSiri) {
Speech.speak(message)
}
return
}
let tierScooters = sortScootersByDistance(allTIERScooters, myLocation)
let voiScooters = sortScootersByDistance(allVOIScooters, myLocation)
console.log('Closest TIER scooter: ' + JSON.stringify(tierScooters[0]))
console.log('Closest VOI scooter: ' + JSON.stringify(voiScooters[0]))
let allScootersByDistance = [...tierScooters, ...voiScooters].sort((a, b) => a.distance - b.distance)
let closest = allScootersByDistance[0]
let addressMessagePart = closest.address ? ` on ${closest.address}` : ''
let resultMessage = `The closest scooter is from ${closest.brand} and is ${Math.round(closest.distance)} m away${addressMessagePart}.`
console.log(resultMessage)
if (config.runsWithSiri) {
Speech.speak(resultMessage)
}
showMap(allScootersByDistance.slice(0, 10), myLocation)
async function getTIERScooters(boundingBox) {
let url = `https://tier.frontend.fleetbird.eu/api/prod/v1.06/map/cars/?lat1=${boundingBox.minLat}&lat2=${boundingBox.maxLat}&lon1=${boundingBox.minLong}&lon2=${boundingBox.maxLong}`
let request = new Request(url)
let scooters = await request.loadJSON()
console.log('Number of available TIER scooters: ' + scooters.length)
return scooters.map(s => {s.brand = 'TIER'; return s})
}
async function getVOIScooters(boundingBox) {
let url = `https://api.voiapp.io/v1/vehicle/status/ready?la=${boundingBox.minLat}&lo=${boundingBox.minLong}`
let request = new Request(url)
let worldwideVOIScooters = await request.loadJSON()
console.log('Number of worldwide VOI scooters: ' + worldwideVOIScooters.length)
let localScooters = worldwideVOIScooters.filter(s => withinBoundingBox(boundingBox, s.location[0], s.location[1]))
console.log('Number of local VOI scooters: ' + localScooters.length)
return localScooters.map(s => {
s.lat = s.location[0] // store location in same format as TIER
s.lon = s.location[1]
s.brand = 'VOI'
return s
})
}
function sortScootersByDistance(scooters, toLocation) {
return scooters.map((s) => {s.distance = 1000 * distance(s.lat, s.lon, toLocation.latitude, toLocation.longitude, 'K'); return s}).sort((a, b) => a.distance - b.distance)
}
function withinBoundingBox(boundingBox, latitude, longitude) {
return latitude > boundingBox.minLat && latitude < boundingBox.maxLat && longitude > boundingBox.minLong && longitude < boundingBox.maxLong
}
function showMap(nearbyScooters, myLocation) {
let closest = nearbyScooters[0]
let html = `
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0">
<meta charset="utf-8">
<style>
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
var map;
function initMap() {
var myLocation = {
lat: ${myLocation.latitude},
lng: ${myLocation.longitude}
}
var closestLocation = {
lat: ${closest.lat},
lng: ${closest.lon}
}
map = new google.maps.Map(document.getElementById('map'), {
center: myLocation,
zoom: 14,
disableDefaultUI: true
});
var voiScale = 0.3
var voiIcon = {
url: "https://s3-eu-west-1.amazonaws.com/shape-website-images/scriptable/voi-marker%402x.png",
scaledSize: new google.maps.Size(132 * voiScale, 160 * voiScale),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(66 * voiScale, 152 * voiScale)
};
var tierIcon = {
url: "https://s3-eu-west-1.amazonaws.com/shape-website-images/scriptable/tier-marker%402x.png",
scaledSize: new google.maps.Size(50, 50),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(25, 48)
};
new google.maps.Marker({
map: map,
position: myLocation,
icon: 'http://www.robotwoods.com/dev/misc/bluecircle.png'
});
var nearbyScooters = ${JSON.stringify(nearbyScooters)}
nearbyScooters.forEach(s => new google.maps.Marker({
map: map,
position: {
lat: s.lat,
lng: s.lon
},
icon: s.brand === 'VOI' ? voiIcon : tierIcon
}))
var directionsService = new google.maps.DirectionsService;
var directionsDisplay = new google.maps.DirectionsRenderer({
suppressMarkers: true
});
directionsDisplay.setMap(map);
directionsService.route({
origin: myLocation,
destination: closestLocation,
travelMode: 'WALKING'
}, function (response, status) {
if (status === 'OK') {
directionsDisplay.setDirections(response);
}
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=${googleApiKey}&callback=initMap" async defer></script>
</body>
</html>
`
let fm = FileManager.iCloud()
let dir = fm.documentsDirectory()
let path = fm.joinPath(dir, 'scriptable_closest_scooter_map.html')
fm.writeString(path, html)
WebView.loadFile(path, new Size(400, 400))
}
// copied from somewhere on interwebs
function distance(lat1, lon1, lat2, lon2, unit) {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
var radlat1 = Math.PI * lat1/180;
var radlat2 = Math.PI * lat2/180;
var theta = lon1-lon2;
var radtheta = Math.PI * theta/180;
var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
if (dist > 1) {
dist = 1;
}
dist = Math.acos(dist);
dist = dist * 180/Math.PI;
dist = dist * 60 * 1.1515;
if (unit=="K") { dist = dist * 1.609344 }
if (unit=="N") { dist = dist * 0.8684 }
return dist;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment