var google = google ? google : null; var DrawMap = (function (debug) { 'use strict'; var initialized = false, waiting = []; var googleMapsSrc = "https://maps.googleapis.com/maps/api/js?key=AIzaSyC44bU63DljgKp4RPFo9v0TH0Hc01LNlbY&callback=DrawMap.Init&libraries=places,geometry", dallasCoords = new Coords(32.7767, -96.7970), activeStrokeHex = "#ffffff", activeFillHex = "#02ff02", inactiveStrokeHex = "#666666", inactiveFillHex = "#f4e842"; insertGoogleMapsJS(); function DrawMap() { } DrawMap.prototype.New = function (cfg, target, initData, returnCB) { return new Map(cfg, target, initData, returnCB); }; DrawMap.prototype.Init = function () { if (initialized) { return; } initialized = true; waiting.forEach(function (fn) { fn(); }); } function Config(obj) { if (!(obj instanceof Object)) { console.error("invalid configuration"); return; } this.readOnly = obj.readOnly === true; this.mapType = obj.mapType || 'roadmap'; this.zoomLevel = obj.zoomLevel != null ? obj.zoomLevel : 9; this.mapTypes = [ MultiSelect.NewEntry("roadmap", "Roadmap"), MultiSelect.NewEntry("satellite", "Satellite"), MultiSelect.NewEntry("hybrid", "Hybrid"), MultiSelect.NewEntry("terrain", "Terrain") ]; this.radiusThresholds = getRadiusThresholds(); function getRadiusThresholds() { var tt = obj.radiusThresholds; if (tt === undefined || tt === null) { return null; } if (!(tt instanceof Array)) { console.error("invalid radius thresholds"); return } var arr = []; tt.forEach(function (obj) { if (!(obj instanceof Object)) { console.error("invalid threshold type"); return; } arr.push(MultiSelect.NewEntry(obj.value, obj.label)); }); return arr; } } function Map(cfg, target, initData, returnCB) { var coords, closed = false, seg = newSegment(initData), es = new Elements(target, { "db": { "target": psStore() }, "container": {}, "mapContainer": {}, "map": {}, "guiModules": {} }), config = new Config(cfg); push(init); this.getSegment = getSegment; this.exit = exit; function init() { insertElements(); setMap(dallasCoords); //getCoords(setMap); if (!!returnCB && typeof returnCB === "function") { returnCB(); } } function insertElements() { es.container = document.createElement("proximity-map"); if (cfg.readOnly) { es.container.classList.add("readOnly"); } es.mapContainer = document.createElement("map-container"); es.container.appendChild(es.mapContainer); target.appendChild(es.container); } function getSegment() { return seg.Dup() } function setMap(c) { coords = c; es.map = new google.maps.Map(es.mapContainer, new GoogleMapOpts(coords, config.zoomLevel, config.mapType)); es.guiModules = new GUIModules(config, es.container, es.map, es.db, seg); } function exit() { if (closed) { return; } removeChild(target, es.container); es.exit(); es = null; closed = true; } } function newSegment(obj) { if (!obj) { return new Segment({}); } return new Segment(obj); } function Segment(obj) { this.name = obj.name || ""; this.ownerID = obj.ownerID || ""; this.idCounter = obj.idCounter || 0; this.locations = obj.locations || []; this.lookback = !isNaN(obj.lookback) ? obj.lookback : -1; this.radiusThreshold = !isNaN(obj.radiusThreshold) ? obj.radiusThreshold : 0; } Segment.prototype.Dup = function () { return newSegment(this); }; Segment.prototype.GetLocation = function (id) { var rl = null; this.locations.some(function (loc) { if (loc.id !== id) { return; } rl = loc; return true; }); return rl; }; function Location(id, label, type, center, radius, points, address) { this.id = id || ""; this.label = label || ""; this.type = type || ""; this.center = center || null; this.radius = radius || null; this.points = points || []; this.address = address || ""; } Location.prototype.Dup = function () { var loc = new Location(this.id, this.label, this.type, this.center, this.radius, this.points.copyWithin(), this.address); return loc; }; Location.fromGeo = (geo, props, radius) => { const cc = geo.coordinates, points = cc.length === 1 ? geo.coordinates[0].map((v) => new Coords(v[1], v[0])) : [new Coords(cc[1], cc[0])]; return new Location("", props.NAMELSAD || props.NAME || 'N/A', geo.type.toLowerCase(), points.length === 1 ? points[0] : null, radius, points); }; function Name(target, seg) { var em = new EventManager(), nc = document.createElement("name-container"), ic = document.createElement("input-container"), p = document.createElement("p"), input = document.createElement("input"); p.textContent = "Name:"; input.type = "text"; input.class = "name"; input.value = seg.name; em.add(input, "change", updateName); ic.appendChild(p); ic.appendChild(input); nc.appendChild(ic); target.appendChild(nc); this.show = show; this.hide = hide; this.exit = exit; function updateName() { seg.name = input.value; } function show() { nc.style.display = ""; } function hide() { nc.style.display = "none"; } function exit() { em.reset(); removeChild(target, nc); nc.innerHTML = ""; em = nc = ic = p = input = null; } } function LookbackPeriod(target, seg) { var pc = document.createElement("period-container"), dd = new Dropdown(pc, { "data": [ { "title": "Lookback period", "value": -1, "placeholder": true }, { "title": "Currently at", "value": 0 }, { "title": "One day", "value": 1 }, { "title": "Seven days", "value": 7 }, { "title": "Fifteen days", "value": 15 }, { "title": "Thirty days", "value": 30 } ], "options": { "dropdownClass": "arctic", "menuItemClass": "arctic", "carrotClass": "dark", }, "initialValue": seg.lookback }, updateLookback); target.appendChild(pc); this.show = show; this.hide = hide; this.exit = exit; function updateLookback(e) { seg.lookback = e; } function show() { pc.style.display = ""; } function hide() { pc.style.display = "none"; } function exit() { removeChild(target, pc); dd.exit(); pc = dd = null; } } function GoogleMapOpts(coords, zoom, mapType) { if (!coords.isValid()) { return } if (isNaN(zoom)) { console.error("Invalid zoom provided", zoom) return } this.zoom = zoom; this.center = new google.maps.LatLng(coords.lat, coords.lng); this.mapTypeId = mapType || "roadmap"; this.streetViewControl = false; this.overviewMapControl = false; this.mapTypeControl = false; } function Coords(lat, lng) { this.lat = lat; this.lng = lng; } Coords.prototype.isValid = function () { if (isNaN(this.lat)) { console.error("Invalid latitude provided", this.lat) return false } if (isNaN(this.lng)) { console.error("Invalid longitude provided", this.lng) return false } return true } function Circle(map, initLocation) { var loc = newLocation(initLocation), cir = new google.maps.Circle({ "strokeColor": activeStrokeHex, "strokeOpacity": 0.8, "strokeWeight": 2, "fillColor": activeFillHex, "fillOpacity": 0.45, "map": map, "center": loc.center, "radius": loc.radius, }); this.getLabel = getLabel; this.getType = getType; this.getLocation = getLocation; this.getID = getID; this.setLabel = setLabel; this.setID = setID; this.setFocus = setFocus; this.unsetFocus = unsetFocus; this.remove = remove; this.render = () => true; this.getCenter = () => loc.center; function newLocation(loc) { if (!loc) { return new Location("", getLabel(), "circle", coords, radius, null); } if (loc.constructor !== Location) { console.error("invalid location type", loc); return } return loc; } function getLabel() { if (!loc.label) { setLabel(); } return loc.label; } function setLabel(str) { loc.label = !!str ? str : loc.center.lat.toFixed(4) + ", " + loc.center.lng.toFixed(4) + " - " + loc.radius + "m"; } function getType() { return "circle"; } function getLocation() { return loc.Dup(); } function getID() { return loc.id; } function setID(id) { loc.id = id; } function setColor(fill, stroke) { cir.setOptions({ "fillColor": fill, "strokeColor": stroke }); } function setFocus() { setColor(activeFillHex, activeStrokeHex); map.setCenter(loc.center); setZoomLevel(map, loc.radius); } function unsetFocus() { setColor(inactiveFillHex, inactiveStrokeHex); } function remove() { cir.setMap(null); } } function Polygon(map, initLocation) { var poly = new google.maps.Polyline({ "map": map, "strokeColor": activeStrokeHex, "strokeOpacity": 0.8, "strokeWeight": 2, }); var pts = new Points(map), loc = newLocation(initLocation), underlay = null; // This is a Circle used for debugging this.getLabel = getLabel; this.getType = getType; this.getLocation = getLocation; this.getCenter = () => getCenter(loc.points); this.getID = getID; this.setLabel = setLabel; this.setID = setID; this.setPaths = setPaths; this.setFocus = setFocus; this.unsetFocus = unsetFocus; this.insertPath = insertPath; this.render = setPolygon; this.remove = remove; function newLocation(loc) { if (!loc) { return new Location("", "", "polygon", null, 0, null); } if (loc.constructor !== Location) { console.error("invalid location type", loc); return } setTimeout(setPolygon, 0); return loc; } function getLabel() { if (!loc.label) { setLabel(); } return loc.label; } function setLabel(str) { loc.label = !!str ? str : loc.center.lat.toFixed(4) + ", " + loc.center.lng.toFixed(4); } function getType() { return "polygon"; } function getLocation() { return loc.Dup() } function insertPath(coords) { loc.points.push(coords); setPaths(loc.points); pts.insert(coords); } function getID() { return loc.id; } function setID(id) { loc.id = id; } function setPaths(paths) { if (isFunction(poly.setPath)) { // Polylines use setPath poly.setPath(paths); } else if (isFunction(poly.setPath)) { // Polygons use setPaths.. poly.setPaths(paths); } } function setColor(fill, stroke) { poly.setOptions({ "fillColor": fill, "strokeColor": stroke }); } function setFocus() { setColor(activeFillHex, activeStrokeHex); map.setCenter(loc.center); setZoomLevel(map, loc.radius); } function unsetFocus() { setColor(inactiveFillHex, inactiveStrokeHex); } function remove() { poly.setMap(null); pts.remove(); if (!!underlay) { underlay.remove(); } } function setPolygon() { if (loc.points.length < 3) { console.error("cannot add polygon with less than three points"); return false; } if (poly !== null) { remove(); } poly = new google.maps.Polygon({ "paths": loc.points, "map": map, "strokeColor": activeStrokeHex, "strokeOpacity": 0.8, "strokeWeight": 2, "fillColor": activeFillHex, "fillOpacity": 0.45 }); loc.center = getCenter(loc.points); loc.radius = getFurthest(loc.center, loc.points); loc.label = getLabel(); if (!!debug) { // Used for debug purposes var dloc = loc.Dup() dloc.label = ""; dloc.type = "circle"; underlay = new Circle(map, dloc); } return true; } function getCenter(coordsArr) { var c = new Coords(0, 0); coordsArr.forEach(function (coord) { c.lat += coord.lat; c.lng += coord.lng; }); c.lat /= coordsArr.length; c.lng /= coordsArr.length; return c; } function getFurthest(center, coordsArr) { var dist = 0, cgll = new GoogleLatLng(center); for (var i = 0; i < coordsArr.length; i++) { var lgll = new GoogleLatLng(coordsArr[i]), d = google.maps.geometry.spherical.computeDistanceBetween(cgll, lgll); if (d > dist) { dist = d; } } return dist; } } function Points(map) { var arr = []; this.insert = insert; this.remove = remove; this.setFocus = setFocus; this.unsetFocus = unsetFocus; function insert(coords) { if (coords.constructor !== Coords) { console.error("invalid type", coords); return; } var pt = new Point(map, coords); arr.push(pt); } function remove() { arr.forEach(function (v) { v.remove(); }); } function setFocus() { arr.forEach(function (v) { v.setFocus(); }); } function unsetFocus() { arr.forEach(function (v) { v.unsetFocus(); }); } } function Point(map, coords) { var cir = new google.maps.Circle({ "strokeColor": '#ff0000', "strokeOpacity": 1, "strokeWeight": 2, "fillColor": '#ff0000', "fillOpacity": 1, "map": map, "center": coords, "radius": getRadius(), }); this.setFocus = setFocus; this.unsetFocus = unsetFocus; this.remove = remove; function setColor(fill, stroke) { cir.setOptions({ "fillColor": fill, "strokeColor": stroke }); } function setFocus() { setColor(activeFillHex, activeStrokeHex); } function unsetFocus() { setColor(inactiveFillHex, inactiveStrokeHex); } function remove() { cir.setMap(null); } function getRadius() { var zoom = map.getZoom(), radius = Math.pow(2, 18 - zoom); if (radius > 1000) { radius = 1000; } else if (radius < 1) { radius = 1; } return radius; } } function GoogleLatLng(coords) { this.lat = function () { return coords.lat }; this.lng = function () { return coords.lng }; } function OptionItems(cfg, opts, map, pubsub, seg) { pubsub.Put("radiusThreshold", seg.radiusThreshold); this.name = new Name(opts, seg); this.lookback = new LookbackPeriod(opts, seg); this.search = new Search(opts, map, pubsub); this.within = new NumericInput(opts, pubsub, "Within", "within"); this.radius = new NumericInput(opts, pubsub, "Radius", "radius"); this.mapType = MultiSelect.New(opts, map, pubsub, "mapType", cfg.mapTypes, function (key) { map.setMapTypeId(key); }); this.radiusThreshold = MultiSelect.New(opts, map, pubsub, "radiusThreshold", cfg.radiusThresholds, function (seg) { return function (value) { seg.radiusThreshold = value; }; } (seg)); this.goBtn = ControlOptsButton.New(opts, pubsub, "Go", "goButton"); } OptionItems.prototype.forEach = function (fn) { var obj = this; Object.keys(obj).forEach(function (key) { fn(obj[key]); }); }; OptionItems.prototype.hideAll = function () { this.forEach(function (item) { item.hide(); }); }; function ControlOpts(target, map, pubsub, seg, cfg) { var opts = document.createElement("control-opts"); opts.appendChild(newHeader()); // We gotta keep it Jewish up in this bitch, you feel me? var oi = new OptionItems(cfg, opts, map, pubsub, seg); oi.hideAll(); target.appendChild(opts); pubsub.Sub("activeControl", updateActive); this.exit = exit; function show() { opts.style.display = ""; } function hide() { opts.style.display = "none"; } function updateActive(e) { var n = 0; oi.search.setEmpty(); if (arrHas(e, "nameInput")) { oi.name.show(); n++; } else { oi.name.hide(); } if (arrHas(e, "lookbackInput")) { oi.lookback.show(); n++; } else { oi.lookback.hide(); } if (arrHas(e, "search")) { oi.search.show(); n++; } else { oi.search.hide(); } if (arrHas(e, "within")) { oi.within.show(); n++; } else { oi.within.hide(); } if (arrHas(e, "radius")) { oi.radius.show(); n++; } else { oi.radius.hide(); } if (arrHas(e, "mapType")) { oi.mapType.show(); n++; } else { oi.mapType.hide(); } if (arrHas(e, "radiusThreshold")) { oi.radiusThreshold.show(); n++; } else { oi.radiusThreshold.hide(); } if (arrHas(e, "goButton")) { oi.goBtn.show(); n++; } else { oi.goBtn.hide(); } if (n > 0) { show(); } else { hide(); } } function newHeader() { var hdr = document.createElement("h3"); hdr.textContent = "Control options"; return hdr; } function exit() { removeChild(target, opts); oi.forEach(function (item) { item.exit(); }); } } function GUIModules(cfg, target, map, pubsub, seg) { var guiModules = document.createElement("gui-modules"), sp = document.createElement("side-panel"), dc = null, co = null, nl = null; if (!cfg.readOnly) { dc = new DrawingControls(guiModules, map, pubsub); co = new ControlOpts(sp, map, pubsub, seg, cfg); dc.set("gotoLocation"); } nl = new NodeList(cfg, sp, map, pubsub, seg); guiModules.appendChild(sp); target.appendChild(guiModules); this.exit = exit; function exit() { removeChild(target, guiModules); if (!cfg.readOnly) { dc.exit(); co.exit(); } nl.exit(); guiModules = sp = dc = co = nl = null; } } function Search(target, map, pubsub) { var search = document.createElement("input-container"), p = document.createElement("p"), input = newInput(), loadGeo = newLoadGeoInput(); p.textContent = "Location:"; search.appendChild(p); search.appendChild(input); search.appendChild(loadGeo); target.appendChild(search); this.setPlaceholder = setPlaceholder; this.setEmpty = setEmpty; this.show = show; this.hide = hide; this.exit = exit; function show() { search.style.display = ""; } function hide() { search.style.display = "none"; } function newInput() { var input = document.createElement("input"); input.type = "text"; input.placeholder = "Enter target location.."; input.addEventListener("change", changeAction); return input; } function newLoadGeoInput() { var label = document.createElement('label'), input = document.createElement("input"), wrap = document.createElement("div"); label.textContent = "Load GeoJSON"; label.classList.add("coButton"); label.htmlFor = "loadGeoJSON" input.id = label.htmlFor input.type = "file"; input.style = 'width: 0.1px; height: 0.1px; opacity: 0; overflow: hidden; position: absolute; z-index: -1'; input.addEventListener("change", loadGeoAction); //input.addEventListener("change", changeAction); wrap.appendChild(document.createElement("br")); wrap.appendChild(input); wrap.appendChild(label) return wrap; } function setPlaceholder(placeholder) { input.placeholder = placeholder; } function changeAction() { pubsub.Put("search", input.value); } function setEmpty() { input.value = ""; } function loadGeoAction(evt) { let fr = new FileReader(), ele = evt.srcElement, f = !!ele.files && !!ele.files[0] ? ele.files[0] : null, radius = 0; if (!f) return; fr.onload = function() { const j = JSON.parse(fr.result), bounds = new google.maps.LatLngBounds(); // map.data.addGeoJson(j); j.features.forEach((v) => { const geo = v.geometry, props = v.properties; if(!geo || !props || !geo.type || !geo.coordinates) { return console.warn('invalid spec:', v); } if(geo.type !== 'Polygon' && geo.type !== 'Point') { return console.warn('invalid type:', geo.type) } if (geo.type === 'Point' && radius === 0) { radius = parseInt(prompt('Please enter the default radius in meters:', 300)); } const loc = Location.fromGeo(geo, props, radius), node = geo.type === 'polygon' ? new Polygon(map, loc) : new Circle(map, loc); if(node.render()) { pubsub.Put("nodes", node); const coords = node.getCenter(); bounds.extend(new google.maps.LatLng(coords.lat, coords.lng)); } }); map.setCenter(bounds.getCenter()); }; fr.readAsText(f); } function exit() { removeChild(target, search); input.addEventListener("change", changeAction); search.innerHTML = ""; } } function NumericInput(target, pubsub, label, key) { var container = document.createElement("input-container"), p = document.createElement("p"), input = newInput(); p.textContent = label + ":"; container.appendChild(p); container.appendChild(input); target.appendChild(container); this.setEmpty = setEmpty; this.show = show; this.hide = hide; this.exit = exit; function show() { container.style.display = ""; } function hide() { container.style.display = "none"; } function setEmpty() { input.value = "0"; } function newInput() { var input = document.createElement("input"); input.type = "number"; input.placeholder = "meters"; input.addEventListener("change", changeAction); return input; } function changeAction() { pubsub.Put(key, parseFloat(input.value)); } function exit() { removeChild(target, container); input.removeEventListener("change", changeAction); container.innerHTML = ""; } } var MultiSelect = (function () { function New(target, map, pubsub, key, selectItems, updateCB) { return new Container(target, map, pubsub, key, selectItems, updateCB); } function Container(target, map, pubsub, key, selectItems, updateCB) { var events = new EventManager(), e = document.createElement("multi-select-container"), msArr = [], active = null, initValue = pubsub.Get(key); selectItems.forEach(insertMultiSelect); target.appendChild(e); pubsub.Sub(key, set); this.show = show; this.hide = hide; this.set = set; this.exit = exit; function show() { e.style.display = ""; } function hide() { e.style.display = "none"; } function set(key) { return msArr.some(function (ms) { if (ms.key === key) { ms.set(); return true; } return false; }); } function insertMultiSelect(entry, n) { var ms = new MultiSelect(e, entry, updateCB); if (!isDefined(initValue) && n === 0 || ms.getValue() === initValue) { selectAction(ms); } events.add(ms.getElement(), "click", function (ms) { return function (e) { e.preventDefault(); selectAction(ms); } } (ms)); msArr.push(ms); } function selectAction(ms) { if (!!active) { active.unsetActive(); } active = ms; active.setActive(); if (!!updateCB) { updateCB(ms.getValue()); } } function exit() { events.reset(); removeChild(target, e); e.innerHTML = ""; } } function MultiSelect(parent, entry, updateCB) { var element = document.createElement("multi-select"), value = entry.value, label = entry.label; element.textContent = entry.label; parent.appendChild(element); this.getElement = function () { return element; }; this.getValue = function () { return value; }; this.getLabel = function () { return label; }; this.setActive = function () { element.classList.add("active"); }; this.unsetActive = function () { element.classList.remove("active"); }; this.remove = function () { removeChild(parent, element); } } function NewEntry(value, label) { return new Entry(value, label); } function Entry(value, label) { this.value = value; this.label = label; } function Exports() { this.New = New; this.NewEntry = NewEntry; } return new Exports; })(); var ControlOptsButton = (function () { function New(target, pubsub, label, key) { return new Button(target, pubsub, label, key); } function Button(target, pubsub, label, key) { var btn = newButton(label); btn.addEventListener("click", clickAction); target.appendChild(btn); this.show = show; this.hide = hide; this.exit = exit; function show() { btn.style.display = ""; } function hide() { btn.style.display = "none"; } function clickAction(e) { e.preventDefault(); pubsub.Put(key, true); } function exit() { removeChild(target, btn); btn.removeEventListener("click", clickAction); } } function Exports() { this.New = New; } function newButton(label) { var btn = document.createElement("a"); btn.classList.add("coButton"); btn.textContent = label; return btn; } return new Exports(); })(); function DrawingControls(target, map, pubsub) { var events = new EventManager(), controls = document.createElement("drawing-controls"), controlList = [ GotoControl, PlacesControl, CircleControl, PolygonControl, MapTypeControl, ThresholdControl, SettingsControl ], cObj = {}, activeControl = null; for (var i = 0; i < controlList.length; i++) { var c = new controlList[i](map, pubsub); if (!isControl(c)) { console.error("Fuck yourself"); return; } events.add(c.getLabel(), "click", function (key) { return function () { action(key); } } (c.getKey())) controls.appendChild(c.getElement()); cObj[c.getKey()] = c; } target.appendChild(controls); google.maps.event.addListener(map, 'click', function (event) { mapClick(new Coords(event.latLng.lat(), event.latLng.lng())); }); this.set = action; this.exit = exit; function mapClick(e) { if (activeControl === null) { return } var c = cObj[activeControl]; c.mapClick(e); } function action(key) { for (var k in cObj) { if (k !== key) { cObj[k].unset(); continue; } var c = cObj[k]; activeControl = c.toggle() ? key : null; pubsub.Put("activeControl", !!activeControl ? c.getControlItems() : []); } } function exit() { events.reset(); removeChild(target, controls); } } function GotoControl(map, pubsub) { var label = "Goto location", key = "gotoLocation", gc = new google.maps.Geocoder(), e = document.createElement("control-element"), labelP = document.createElement("p"), active = false, psk = null; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.mapClick = insert; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["search", "goButton"]; }; function insert(coords) { return null; } function searchAction() { gc.geocode({ "address": pubsub.Get("search") }, moveMap); } function moveMap(results, status) { if (status !== "OK") { return } var result = results[0]; map.setCenter(new Coords(result.geometry.location.lat(), result.geometry.location.lng())); } function set() { if (!!active) { return; } active = true; e.classList.add("active"); psk = pubsub.Sub("goButton", searchAction); } function unset() { if (!active) { return; } active = false; e.classList.remove("active") pubsub.Unsub("goButton", psk); psk = null; } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function PlacesControl(map, pubsub) { var label = "Add places", key = "addPlaces", pl = new google.maps.places.PlacesService(map), e = document.createElement("control-element"), labelP = document.createElement("p"), active = false, psk = null; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.mapClick = insert; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["search", "radius", "within", "goButton"]; }; function insert(coords) { return null; } function searchAction(v) { var radius = pubsub.Get("radius"), within = pubsub.Get("within"), query = pubsub.Get("search"); if (!radius || !within || !query) { // Error stuff here return } var req = { location: map.getCenter(), radius: radius, query: query, }; pl.textSearch(req, function (results, status) { if (status !== "OK") { // Error stuff here return } results.forEach(function (result) { var center = new Coords(result.geometry.location.lat(), result.geometry.location.lng()), loc = new Location("", result.name, "circle", center, pubsub.Get("radius"), null, result.formatted_address); pubsub.Put("circles", loc); }); }) } function set() { if (!!active) { return; } active = true; e.classList.add("active"); psk = pubsub.Sub("goButton", searchAction); } function unset() { if (!active) { return; } active = false; e.classList.remove("active") pubsub.Unsub("search", psk); psk = null; } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function CircleControl(map, pubsub) { var label = "Draw circle", key = "drawCircle", e = document.createElement("control-element"), labelP = document.createElement("p"), active = false; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.mapClick = insert; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; pubsub.Sub("circles", listen); function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["radius"]; } function listen(loc) { var cir = new Circle(map, loc); pubsub.Put("activeNode", ""); pubsub.Put("nodes", cir); } function insert(coords) { var cir = getNode(coords); if (cir === null) { console.error("invalid circle radius", pubsub.Get("radius")); return; } pubsub.Put("activeNode", ""); pubsub.Put("nodes", cir); } function getNode(coords) { var rad = pubsub.Get("radius"); if (typeof rad !== "number" || rad === 0) { // Do error here return null; } var loc = new Location("", "", "circle", coords, rad); return new Circle(map, loc); } function set() { if (!!active) { return; } active = true; e.classList.add("active"); } function unset() { if (!active) { return; } active = false; e.classList.remove("active") } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function PolygonControl(map, pubsub) { var label = "Draw polygon", key = "drawPolygon", e = document.createElement("control-element"), labelP = document.createElement("p"), active = false, poly = null; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.mapClick = mapClick; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; pubsub.Sub("polygons", listen); pubsub.Sub("goButton", renderPoly); function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["goButton"]; } function listen(coords) { insert(coords); } function insert(coords) { pubsub.Put("nodes", getNode(coords)); } function mapClick(coords) { if (poly === null) { pubsub.Put("activeNode", ""); poly = new Polygon(map); } poly.insertPath(coords); } function getNode(coordsArr) { return new Polygon(map, coordsArr); } function set() { if (!!active) { return; } active = true; e.classList.add("active"); } function unset() { if (!active) { return; } active = false; e.classList.remove("active"); if (poly !== null) { poly.remove(); poly = null; } } function renderPoly() { if (poly === null) { return; } if (poly.render()) { pubsub.Put("nodes", poly); poly = null; } } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function MapTypeControl(map, pubsub) { var label = "Select map type", key = "selectMapType", e = document.createElement("control-element"), labelP = document.createElement("p"), active = false; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; this.mapClick = function () { }; function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["mapType"]; } function set() { if (!!active) { return; } active = true; e.classList.add("active"); } function unset() { if (!active) { return; } active = false; e.classList.remove("active") } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function ThresholdControl(map, pubsub) { var label = "Radius threshold", key = "radiusThreshold", e = document.createElement("control-element"), labelP = document.createElement("p"), active = false; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; this.mapClick = function () { }; function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["radiusThreshold"]; } function set() { if (!!active) { return; } active = true; e.classList.add("active"); } function unset() { if (!active) { return; } active = false; e.classList.remove("active") } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function SettingsControl(pubsub) { var label = "Segment settings", key = "segmentSettings", e = document.createElement("control-element"), labelP = document.createElement("p"), active = false; e.appendChild(labelP); e.classList.add(key); labelP.textContent = label this.getElement = getElement; this.getLabel = getLabel; this.getKey = getKey; this.getControlItems = getControlItems; this.set = set; this.unset = unset; this.toggle = toggle; this.mapClick = function () { }; function getElement() { return e; } function getLabel() { return labelP; } function getKey() { return key; } function getControlItems() { return ["nameInput", "lookbackInput"]; } function set() { if (!!active) { return; } active = true; e.classList.add("active"); } function unset() { if (!active) { return; } active = false; e.classList.remove("active") } function toggle() { if (active) { unset(); return false; } else { set(); return true } } } function NodeList(cfg, target, map, pubsub, seg) { var isHidden = false, events = new EventManager(), nl = document.createElement("node-list"), arr = [], currentID = 0; this.getElement = getElement; this.insert = insert; this.exit = exit; nl.appendChild(newHeader()); target.appendChild(nl); pubsub.Sub("nodes", insertListen); pubsub.Sub("activeNode", activeListen); hide(); processInitData(); function insertListen(node) { insert(node); show(); } function activeListen(id) { setActive(id); } function processInitData() { if (!seg || !seg.locations || seg.locations.length === 0) { return; } seg.locations.forEach(function (item) { var loc = new Location(item.id, item.label, item.type, item.center, item.radius, item.points); switch (item.type) { case "circle": pubsub.Put("nodes", new Circle(map, loc)); break; case "polygon": pubsub.Put("nodes", new Polygon(map, loc)); break; default: console.error("invalid node type"); return; } }) setActive(seg.locations[0].id) } function unsetActive() { pubsub.Put("activeNode", ""); } function show() { if (!isHidden) { return; } nl.style.display = ""; isHidden = false; } function hide() { if (isHidden) { return; } nl.style.display = "none"; isHidden = true; } function getElement() { return nl; } function insert(n) { var node = new Node(n); arr.push(node); nl.appendChild(node.getElement()); } function setActive(id) { for (var i = 0; i < arr.length; i++) { var n = arr[i]; if (n.getID() === id) { n.setFocus() } else { n.unsetFocus(); } } } function newHeader() { var hdr = document.createElement("h3"); hdr.textContent = "Locations"; return hdr; } function Node(value) { var e = document.createElement("node"), p = document.createElement("p"), input = document.createElement("input"), rem = !cfg.readOnly ? document.createElement("remove") : null, label = value.getLabel(), id = value.getID(); if (id === "") { id = seg.idCounter.toString() value.setID(id); seg.idCounter++; seg.locations.push(value.getLocation()) } p.textContent = label; input.style.display = "none"; input.value = label; input.type = "text"; e.appendChild(p); e.appendChild(input); if (!cfg.readOnly) { e.appendChild(rem); events.add(rem, "click", remove); } var loc = value.getLocation(); if (!!loc.address) { var addr = document.createElement("address"); addr.textContent = loc.address; e.appendChild(addr); } this.getElement = getElement; this.getID = getID; this.setFocus = setFocus; this.unsetFocus = unsetFocus; events.add(e, "click", setActive); if (!cfg.readOnly) { events.add(e, "dblclick", setEdit); events.add(input, "change", editLabel); } function getElement() { return e; } function getID() { return id; } function editLabel() { var ll = seg.GetLocation(id); // TODO (Josh): Look into cleaning this up so we are just using one reference to location per node label = p.textContent = ll.label = input.value; value.setLabel(label); setView(); } function setActive(e) { if (e.target === rem) { return } pubsub.Put("activeNode", id); } function setEdit(e) { if (e.target === rem || e.target === input) { return } p.style.display = "none"; input.style.display = ""; input.focus(); } function setView() { p.style.display = ""; input.style.display = "none"; } function setFocus() { e.classList.add("focused"); value.setFocus(); } function unsetFocus() { e.classList.remove("focused"); value.unsetFocus(); setView(); } function remove() { nl.removeChild(e); value.remove(); arr.some(function (n, i) { if (n.getID() !== id) { return } arr.splice(i, 1); seg.locations.splice(i, 1); return true; }); if (arr.length === 0) { hide(); } } } function exit() { events.reset(); removeChild(target, nl); } } function capitalizeN(n, s) { if (s == null || !s.length) return s; return (n !== 0 ? s.substring(0, n) : '') + s.substring(n, n + 1).toUpperCase() + s.substring(n + 1); } function isDefined(v) { return v !== void 0 && v !== null; } function isControl(type) { if (!isFunction(type.getElement)) { return false; } if (!isFunction(type.getKey)) { return false; } if (!isFunction(type.getLabel)) { return false; } if (!isFunction(type.set)) { return false; } if (!isFunction(type.unset)) { return false; } if (!isFunction(type.toggle)) { return false; } if (!isFunction(type.mapClick)) { return false; } return true; } function isFunction(fn) { return !!fn && typeof fn === "function"; } function arrHas(arr, s) { for (var i = 0; i < arr.length; i++) { if (arr[i] === s) { return true; } } return false; } function insertGoogleMapsJS() { if (!!google && !!google.maps && !!google.maps.places) { setTimeout(DrawMap.Init); return; } var s = document.createElement("script"); s.type = "application/javascript"; s.src = googleMapsSrc; document.head.appendChild(s); } function getCoords(cb) { navigator.geolocation.getCurrentPosition( function (position) { cb(new Coords(position.coords.latitude, position.coords.longitude)); }, function () { console.error("Error encountered while retrieving lat/lon, reverting to fallback") cb(dallasCoords); } ) } function setZoomLevel(map, radius) { if (radius < 100) { map.setZoom(16); } else if (radius < 500) { map.setZoom(14); } else if (radius < 1000) { map.setZoom(12); } else if (radius < 5000) { map.setZoom(11); } else if (radius < 10000) { map.setZoom(10); } else if (radius < 15000) { map.setZoom(8); } else { map.setZoom(7); } } function push(fn) { if (initialized) { fn(); return } waiting.push(fn); } return new DrawMap(); })(false);