Created
September 5, 2018 01:14
-
-
Save joegaudet/ea24a28c34fae003d4238036ed4efa7a to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Ember from 'ember'; | |
import ENV from 'star-fox/config/environment'; | |
const { | |
Object, | |
Component, | |
run, | |
observer | |
} = Ember; | |
const { | |
Map, | |
Polygon | |
} = google.maps; | |
/** | |
* This component wraps the google map view and allows for simple editing of a polygon | |
*/ | |
export default Component.extend({ | |
classNames: 'fde-map-control', | |
/** @type {LatLng[]} */ | |
value: [], | |
/** | |
* @callback changeHandler | |
* @property {LatLong[]} startDate as a string | |
* | |
* @type {changeHandler} | |
*/ | |
onChange(){}, | |
/** @type {string} */ | |
citySearchText: '', | |
/** @type {boolean} */ | |
allowsPolygonEditing: true, | |
didInsertElement(){ | |
const canvas = this.$('.g-map-canvas').get(0); | |
const latLngs = this.get('value').map(({lat, long}) => ({lat, lng: long})); | |
// center of a polygon... roughly is the average of the lats and longs | |
// now that I think about it, it's probably better to find the min and | |
// maxes and divide by / 2 but this actually work most of the time :P | |
const coords = latLngs.reduce((acc, latLng) => { | |
acc.centerLat += latLng.lat; | |
acc.centerLng += latLng.lng; | |
return acc; | |
}, { | |
centerLat: 0, | |
centerLng: 0 | |
}); | |
coords.centerLat /= latLngs.length; | |
coords.centerLng /= latLngs.length; | |
// construct the map | |
const map = new Map(canvas, { | |
// protocol: 'https', | |
key: ENV['g-map'].key, | |
libraries: ['places', 'geometry'], | |
zoom: 12, | |
center: {lat: coords.centerLat || 0, lng: coords.centerLng || 0}, | |
mapTypeId: 'terrain' | |
}); | |
this.set('map', map); | |
this._fitToBounds(latLngs); | |
// init places service for handling the map lookups | |
const placesService = new google.maps.places.PlacesService(map); | |
this.set('placesService', placesService); | |
if(this.get('allowsPolygonEditing')){ | |
// Construct the polygon. | |
const polygon = new Polygon({ | |
paths: latLngs, | |
editable: !this.get('isReadonly'), | |
strokeColor: '#FF0000', | |
strokeOpacity: 0.8, | |
strokeWeight: 2, | |
fillColor: '#FF0000', | |
fillOpacity: 0.35 | |
}); | |
this.set('polygon', polygon); | |
polygon.setMap(map); | |
// setup events | |
this.setupPolygonEvents(); | |
} | |
}, | |
willDestroyElement(){ | |
const polygon = this.get('polygon'); | |
const map = this.get('polygon'); | |
// cleanup | |
google.maps.event.clearListeners(polygon); | |
google.maps.event.clearListeners(map); | |
delete this.map; | |
delete this.polygon; | |
}, | |
/** | |
* | |
*/ | |
setupPolygonEvents: function () { | |
const polygon = this.get('polygon'); | |
/** | |
* Register right click handler for deleting verticies | |
*/ | |
google.maps.event.addListener(polygon, 'rightclick', this._handleRightClick.bind(this)); | |
/** | |
* Register mouse up to fire change events | |
*/ | |
google.maps.event.addListener(polygon, 'mouseup', this._handleMouseUp.bind(this)); | |
}, | |
/** | |
* @param {event} e | |
* @private | |
*/ | |
_handleRightClick(e){ | |
const map = this.get('map'); | |
const polygon = this.get('polygon'); | |
const deleteMenu = new DeleteMenu(this); | |
// Check if click was on a vertex control point | |
if (e.vertex == undefined) { | |
return; | |
} | |
deleteMenu.open(map, polygon.getPath(), e.vertex); | |
}, | |
/** | |
* @private | |
*/ | |
_handleMouseUp(){ | |
run.next(_ => this.fireOnChange()); | |
}, | |
/** | |
* @param {LatLng[]} latLngs | |
* @private | |
*/ | |
_fitToBounds(latLngs) { | |
if (latLngs.length > 0) { | |
const bounds = new google.maps.LatLngBounds(); | |
latLngs.forEach((point) => bounds.extend(point)); | |
this.get('map').fitBounds(bounds); | |
} | |
}, | |
/** | |
* Observer for watching incoming changes and translating them to lat lng and updating the map | |
*/ | |
valueDidChange: observer('value', function () { | |
const latLngs = this.get('value').map(({lat, long}) => ({lat, lng: long})); | |
this.get('polygon') | |
.setPath(latLngs); | |
}), | |
/** | |
* Push onChange events to lat / long which is being used throughout starfox | |
*/ | |
fireOnChange() { | |
const newValue = this | |
.get('polygon') | |
// get the polygon path in an es6 array | |
.getPath() | |
.getArray() | |
// We use Lat and Long pretty much everywhere an google likes the symetry of lat/lng | |
.map(_ => Object.create({lat: _.lat(), long: _.lng()})); | |
this.onChange(newValue); | |
}, | |
actions: { | |
/** | |
* Searches for a city and centers the map on that place (the city could be any google place) | |
*/ | |
centerOnCity(){ | |
const citySearchText = this.get('citySearchText'); | |
if (citySearchText) { | |
const request = { | |
query: citySearchText | |
}; | |
this.get('placesService') | |
.textSearch(request, (results, status) => { | |
if (status == google.maps.places.PlacesServiceStatus.OK) { | |
const {geometry: {location: {lat, lng}}} = results[0]; | |
const map = this.get('map'); | |
map.setCenter({lat: lat(), lng: lng()}); | |
map.setZoom(12); | |
} | |
}); | |
} | |
}, | |
/** | |
* Set the polygon to a box around the center of the map. | |
*/ | |
setBoxAroundCenter() { | |
const map = this.get('map'); | |
const center = map.getCenter(); | |
const x = center.lng(); | |
const y = center.lat(); | |
const box = [ | |
{lat: y - 0.05, lng: x - 0.05}, | |
{lat: y - 0.05, lng: x + 0.05}, | |
{lat: y + 0.05, lng: x + 0.05}, | |
{lat: y + 0.05, lng: x - 0.05}, | |
{lat: y - 0.05, lng: x - 0.05} | |
]; | |
this | |
.get('polygon') | |
.setPath(box); | |
this.fireOnChange(); | |
} | |
} | |
}); | |
/** | |
* A menu that lets a user delete a selected vertex of a path. I stole this from the google examples | |
* It's not ember, but will only be used inside the context of this app. | |
* | |
* https://developers.google.com/maps/documentation/javascript/examples/delete-vertex-menuA | |
* | |
* @constructor | |
*/ | |
function DeleteMenu(component) { | |
this._component = component; | |
this.div_ = document.createElement('div'); | |
this.div_.className = 'fde-gmap-delete-menu'; | |
this.div_.innerHTML = 'Delete'; | |
var menu = this; | |
google.maps.event.addDomListener(this.div_, 'click', function () { | |
menu.removeVertex(); | |
}); | |
} | |
DeleteMenu.prototype = new google.maps.OverlayView(); | |
DeleteMenu.prototype.onAdd = function () { | |
var deleteMenu = this; | |
var map = this.getMap(); | |
this.getPanes().floatPane.appendChild(this.div_); | |
// mousedown anywhere on the map except on the menu div will close the | |
// menu. | |
this.divListener_ = google.maps.event.addDomListener(map.getDiv(), 'mousedown', function (e) { | |
if (e.target != deleteMenu.div_) { | |
deleteMenu.close(); | |
} | |
}, true); | |
}; | |
DeleteMenu.prototype.onRemove = function () { | |
google.maps.event.removeListener(this.divListener_); | |
this.div_.parentNode.removeChild(this.div_); | |
this._component.fireOnChange(); | |
// clean up | |
this.set('position'); | |
this.set('path'); | |
this.set('vertex'); | |
}; | |
DeleteMenu.prototype.close = function () { | |
this.setMap(null); | |
}; | |
DeleteMenu.prototype.draw = function () { | |
var position = this.get('position'); | |
var projection = this.getProjection(); | |
if (!position || !projection) { | |
return; | |
} | |
var point = projection.fromLatLngToDivPixel(position); | |
this.div_.style.top = point.y + 'px'; | |
this.div_.style.left = point.x + 'px'; | |
}; | |
/** | |
* Opens the menu at a vertex of a given path. | |
*/ | |
DeleteMenu.prototype.open = function (map, path, vertex) { | |
this.set('position', path.getAt(vertex)); | |
this.set('path', path); | |
this.set('vertex', vertex); | |
this.setMap(map); | |
this.draw(); | |
}; | |
/** | |
* Deletes the vertex from the path. | |
*/ | |
DeleteMenu.prototype.removeVertex = function () { | |
var path = this.get('path'); | |
var vertex = this.get('vertex'); | |
if (!path || vertex == undefined) { | |
this.close(); | |
return; | |
} | |
path.removeAt(vertex); | |
this.close(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment