From ed52f6dff3ed01d6f67e56e7d8cae468db5dc5c3 Mon Sep 17 00:00:00 2001 From: bruce mcpherson Date: Wed, 24 May 2017 11:44:32 +0000 Subject: [PATCH] initial --- .gitignore | 1 + README.md | 4 + img/exn64.ico | Bin 0 -> 4286 bytes index.html | 110 ++++++++ lib/effexapiclient.js | 201 ++++++++++++++ src/main.js | 72 +++++ src/maps.js | 633 ++++++++++++++++++++++++++++++++++++++++++ src/yourMapsApi.js | 4 + 8 files changed, 1025 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 img/exn64.ico create mode 100644 index.html create mode 100644 lib/effexapiclient.js create mode 100644 src/main.js create mode 100644 src/maps.js create mode 100644 src/yourMapsApi.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..83adf32 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +src/mapsApiKey.js diff --git a/README.md b/README.md new file mode 100644 index 0000000..812b5b7 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# effex-demo-markers +A demo using the Ephemeral Exchange API to connect a Maps app to various back ends + +Read more about this at http://ramblings.mcpher.com/Home/excelquirks/ephemeralexchange/sheetsmaps diff --git a/img/exn64.ico b/img/exn64.ico new file mode 100644 index 0000000000000000000000000000000000000000..577caaecc070482c00e5a525e85cb807d8d33c06 GIT binary patch literal 4286 zcmbu@dsGuw9tZI0*+2TvuJ-iUt|*ABfW@|FeO7Jd4JwEr@)QK3)uPr`-St)9r`jr3 zTOV7sD(VX8E{doXd?N^$5D*ASAR!=+1PKZi@P!ca-QS&LFhNAy9nQ=hNSM$4{_dTd z0gii%zJ?CvXdBA?=N*oFpUytuzvDP}YUx!v(l(So{-MLo^q~VwGKa@0a~w~WP>?%vo_fP(>IAX!>^Rxf@hmc-=NpfY#SVfNG+49>XI@RJ4 zu-wbUAK;E;G;*bGxLW9sb48IjSrUiCB`Z3AEZ%fx!|iROd%5Hr{U{^p-F~H64u>dX zm!&D3E6FaDj6sgn9ajoxblVA~E3r@UE#k{|W0NYOYq|2&fq>kEzq}D@ev|m3y|Xgc zQ7u@Ot%_=3vd7|<*-k5pLUPGM94TFey^1Z^&f7<^s^T=3R9?nhO|Htn^fsFkuid1V z?8cYpIW=3F!a1`QVrLXc#vxnkiOYq7FxiVN>^<11I*OGQXYgg^70kUWMnsJQGpehb zKU39>>#GBIuGu^P9{X%jH054|!=j6u@IBVE=)omxEU=-1F~BOA}UUWe`?NRan&`5s`C+6TLyov4qo-GFYE%7?Q@|WQym!`j-Xtl=S4(a zQ}RCRgXcVhsRqNl;8Fd^l)KCTk9^84*okI)$0G=K@cf6E71oeUpCwwM<}#|eB!6Oe z?$h15*WpLycC3!iz}z8vh@2YWE;sOfa6N(UF>pK81y`lnzD~K*o?#lZ_dkYzJlPv4 z_p%l+J2bo*A%1!}tv4Mkw@myI*^`i2FclZ?gdnAOF8(e18VBSidu#cAtT>fUegof! zpp`VYDZfiKjf1_Z51t~bfrN4k_6ch{h}oZ$9oCF+za~UXzSmk#_Z_pvR`xuymy*2! zyOcYyrThTZ{##tn9f_UuckzC#gC6b*Qw=<3n|v=PBfPGfb2IyAbMEzR2wG}wN4nfc z)F{1?=D9JSulW?3Yr!~8xld5;gOvLxWjwy4-0RheNX?%DMJ9)m%Wq@lrN71(0b7xPhqL%B20EMt+SQz)iuJc{wbf^ zt+W#!d31kYd5$S(o?%Xs3^|wWaO0daLf79z&{t%OenV&o)tu%2toPjJ+Mf|VgVz-Q zJbsP(l+k40qTI}`!Zh7|vY)}_E{(6*_P*TC+tZHG{k``wW^8Lk;M!IzJ7Ox?pOXDC z)qI$B&6zE-(;GquSBw&T)-EnTcWlAkTZke%sHZJ6<>u|vw5AI7_mBFtWB>ND&1WB6 zXZ8HS{LsRb{s6uez5M*e`rxZ58abt|vANqWp=TCmU!t0)Q0{~$@Y_Q*-)gqMBs=yugv}z`=K&%o zQO!TnRz8ena{Q{DICfVLp^NIHn{oyNWZr9KiJ*f6jzyQ_n`uoeWC+=JCjq}_5FfDLpiy>&y zLxfn^Q4`1>Nw&T9JZEdm99!+#@Bw9GEU)$doclzm1_eaw76k|D)51L(?ni#o*g3~p zZ;TnzV2rh^?})Y2>7s3QDN%!~n8i$H{}03SA!q;q literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..0e20722 --- /dev/null +++ b/index.html @@ -0,0 +1,110 @@ + + + + effex-demo-markers + + + + + + + + + + + + + +
+
+ +
+
+

Effex store data

+
+
Updater key
+
+
Item key
+
+
+
+
+
Collecting data using maps
+

    +
  • Clicking on a marker allows you to modify its contents, or to delete it
  • . +
  • Right click somewhere on the map to drop in a new marker"
  • +
+
+
+
When finished, update the store to send back changes
+

+ +
+ +
+
+ + + + + + + + + + + + + diff --git a/lib/effexapiclient.js b/lib/effexapiclient.js new file mode 100644 index 0000000..ad69db4 --- /dev/null +++ b/lib/effexapiclient.js @@ -0,0 +1,201 @@ +/** + * @namespace EffexApiClient + * lite effex API client, with no admin capabilities + */ +/* global axios */ +var EffexApiClient = (function(ns) { + + // the api base url + var ax,keys; + + ns.setKeys = function (optionalKeys){ + keys = optionalKeys || ns.getUriKeys(); + }; + + ns.getKeys = function () { + return keys; + }; + + ns.setBase = function(base) { + ax = axios.create({ + baseURL: base, + maxContentLength: 512000 + }); + }; + + + + function clone (ob) { + return JSON.parse(JSON.stringify(ob || {})); + } + + /** + * turns a params object into a url + * @param {object} params the params + * @return {string} the uri + */ + function makeParams(params) { + params = params || {}; + var pa = Object.keys(params).reduce(function(p, c) { + p.push(c + "=" + encodeURIComponent(params[c])); + return p; + }, []); + + return pa.length ? ("?" + pa.join("&")) : ""; + } + + ns.checkKeys = function (preview) { + if (!Array.isArray(preview)) preview = [preview]; + return preview.every(function(d){ return keys[d]}); + }; + + /** + * @param {string} boss the boss key + * @param {string} mode the type like writer/reader/updater + * @param {object} params the params + * @return {Promise} to the result + */ + ns.generateKey = function (boss, mode,params) { + return ax.get ('/' + boss + '/' + mode + makeParams(params)); + }; + + /** + * ping the service + * @return {object} "PONG" + */ + ns.ping = function() { + return ax.get('/ping'); + }; + + /** + * info the service + * @return {object} result + */ + ns.info = function() { + return ax.get('/info'); + }; + + /** + * get quotas + * @return {object} the quotas + */ + ns.getQuotas = function() { + return ax.get('/quotas'); + }; + + /** + * update an item + * @param {string} id the item id + * @param {string} updater the updater key + * @param {object} data what to write + * @param {string} method the to use (post,get) + * @param {object} params the params + * @return {Promise} to the result + */ + ns.update = function (data, id, updater, method , params) { + method = (method || "post").toLowerCase(); + params = params || {}; + + if (method === "get") { + params = clone(params); + params.data = JSON.stringify(data); + } + var url = "/updater/" + ns.checkKey("updater",updater) + "/" + ns.checkKey("item",id) + makeParams(params); + return ax[method] (url, {data:data}); + }; + + /** + * @param {string} writer the writer key + * @param {object} data what to write + * @param {string} method the to use (post,get) + * @param {object} params the params + * @return {Promise} to the result + */ + ns.write = function (data, writer, method , params) { + method = (method || "post").toLowerCase(); + params = params || {}; + + if (method === "get") { + params = clone(params); + params.data = JSON.stringify(data); + } + var url = "/writer/" + ns.checkKey("writer",writer) + makeParams(params); + return ax[method] (url, {data:data}); + }; + + + ns.checkKey = function (type, value) { + var k= value || keys[type]; + if (!k) console.log ("failed key check", type, value); + return k; + }; + + + /** + * @param {string} id the item id + * @param {string} writer the writer key + * @param {object} params the params + * @return {Promise} to the result + */ + ns.remove = function (id, writer , params) { + return ax.remove ('/writer/' + ns.checkKey("writer",writer) + '/' + ns.checkKey("item",id) + makeParams(params || [])); + }; + + ns.read = function (id, reader , params) { + params = params || {}; + id = id || keys.item; + reader = reader || keys.reader; + return ax.get ('/reader/' + ns.checkKey("reader",reader) + '/' + ns.checkKey("item",id) + makeParams(params)); + }; + + /** + * @param {string} coupon the coupon code + * @return {Promise} to the result + */ + ns.validateKey = function (coupon) { + return ax.get ('/validate/' + coupon); + }; + + /** + * @param {string} id the item id + * @param {string} writer the writer key + * @param {string} key the key to assign the alias for + * @param {string} alias the alias to assign + * @param {object} params the params + * @return {Promise} to the result + */ + ns.registerAlias = function (writer, key, id , alias, params) { + return ax.get('/'+ ns.checkKey("writer",writer) + '/' + key + + '/alias/' + encodeURIComponent(alias) + '/' + ns.checkKey("item",id) + makeParams(params)); + }; + + ns.getUriParam = function (name, source) { + var match = RegExp('[?&]' + name + '=([^&]*)').exec(source || window.location.search); + return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + }; + + /** + * standard parameters that might useful for an effex app + * @return {object} keys + */ + ns.getUriKeys = function() { + var ob = ["updater", "reader", "item","boss","writer"] + .reduce(function(p, c) { + p[c] = ns.getUriParam(c); + return p; + }, {}); + + // updaters/writers can standin for readers + ob.updater = ob.updater || ob.writer; + ob.reader = ob.reader || ob.updater || ob.writer; + + return ob; + }; + + // default prod + ns.setBase("https://ephex-auth.appspot-preview.com"); + ns.setKeys(); + axios.defaults.headers.post['Content-Type'] = 'application/json'; + + return ns; +})({}); diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..669b6ff --- /dev/null +++ b/src/main.js @@ -0,0 +1,72 @@ +/* global google */ +/* global Maps */ +/* global EffexApiClient */ +/* global getMapsApiKey */ + +google.load('maps', '3', { + callback: function() { + + // this particular app is expecting to read and update an item, so ensure we have keys for that + if (!EffexApiClient.checkKeys(["updater", "reader", "item"])) { + errify('missing uri parameters', 'both an updater key and an item key are required'); + } + + else { + // give some info + document.getElementById("efx-id").innerHTML = EffexApiClient.getKeys().item; + document.getElementById("efx-key").innerHTML = EffexApiClient.getKeys().updater; + + var maps; + // and get the data + EffexApiClient.read() + .then(function(response) { + if (response.data && response.data.ok) { + // initialize the map and get going + maps = new Maps().init(response.data); + } + else { + errify("Failed to get effexdata", JSON.stringify(response)); + } + }) + .catch(function(err) { + errify("grevious error getting data", err); + }); + + // hook up the update button + document.getElementById("update") + .addEventListener("click", function() { + EffexApiClient.update(maps.spots.map(function(d) { + return d.place; + })) + .then(function(result) { + if (result.data && result.data.ok) { + errify("Effex updated", maps.spots.length + " spots sent"); + } + else { + errify("Failed to update effexdata", JSON.stringify(result)); + } + }) + .catch(function(err) { + errify("grevious error updating data", err); + }); + }); + } + + + }, + + + other_params: 'key=' + getMapsApiKey() + +}); + +// error messages +function errify(message, error) { + var ef = document.getElementById("errify"); + ef.classList.remove ("mui--hide"); + ef.innerHTML = message + '
' + error; + console.log(message, error); + setTimeout(function () { + ef.classList.add ("mui--hide"); + }, 5000); +} diff --git a/src/maps.js b/src/maps.js new file mode 100644 index 0000000..a3f5e3a --- /dev/null +++ b/src/maps.js @@ -0,0 +1,633 @@ + +/*global google*/ +/*global errify*/ +/*global getMapsApiKey*/ + +/** + * @constructor Maps + * all maps type things will be done here + */ +var Maps = function () { + var ns = this; + + // the map + ns.map = null; + + // the streetview panorama + ns.pano = null; + + // the spots known + ns.spots = []; + + // the spot with an activewindow + ns.activeSpot = null; + + // the streeviewservice + ns.sv = new google.maps.StreetViewService; + + /** + * remove a spot + * @param {object} spot the place + */ + ns.removeSpot = function (spot) { + ns.spots = ns.spots.filter (function (d) { + return spot.maps.id !== d.maps.id; + }); + + }; + + /** + * uses the streetview service to detect nearby panorama + * @param {number} [meters=50] how far to look around + * @param {object} spot where the marker is + * @return {Promise} the result + */ + ns.getPanoByLocation = function (meters,spot) { + + meters = meters || 50; + var markerPosition = new google.maps.LatLng(spot.place.lat, spot.place.lng); + + // if we already have a pano for this spot, then we'll recreate the last known thing + var sr = spot.maps.sv; + + if (sr) { + // always set the marker position + //ns.pano.setPosition (sr.result.location); + + // and the pano + ns.pano.setPano(sr.pano); + + // point of view last time we were here + ns.pano.setPov ({ + heading:sr.pov.heading, + pitch:sr.pov.pitch + }); + ns.pano.setZoom (sr.zoom); + + return Promise.resolve (ns.pano); + } + + + // this'll be where search + return new Promise (function (resolve, reject) { + + //https://developers.google.com/maps/documentation/javascript/3.exp/reference#StreetViewService + ns.sv.getPanorama ({ + location:markerPosition, + preference:google.maps.StreetViewPreference.BEST, + radius:meters, + source:"outdoor" + } , function (result,status) { + if (result && status === "OK") { + + var cameraPosition = result.location.latLng; + var zoom = 1; + // set the pitch to 0, but inherit the last zoom used. + // the heading can be used to adjust the cameraposition towards the marker + // https://developers.google.com/maps/documentation/javascript/geometry#Navigation + spot.maps.sv = { + pov: { + heading:google.maps.geometry.spherical.computeHeading(cameraPosition , markerPosition), + pitch:0 + }, + zoom:zoom + }; + + // point that at the pano + var pov = spot.maps.sv.pov; + ns.pano.setPano(result.location.pano); + ns.pano.setPov ({ + heading:pov.heading, + pitch:pov.pitch + }); + ns.pano.setZoom (spot.maps.sv.zoom); + resolve (ns.pano); + } + else { + reject (status); + } + }); + }); + + }; + + /** + * find street view and show it + * @param {object} spot the spot to show + */ + ns.showStreetView = function(spot) { + + ns.getPanoByLocation(50, spot) + .then(function(pano) { + ns.pano.setVisible(true); + ns.showElem("camera", true); + }) + .catch(function(err) { + errify("streetview - no images", err); + }); + }; + + /** + * make an infowindow + * @param {object} spot a place + * @return {infoWindow} the infowindow + */ + ns.makeInfoWindow = function (spot) { + var place = spot.place; + var marker = spot.maps.marker; + ns.activeSpot = spot; + + var content ='
'; + + + content += '
'; + content += ''; + content += ''; + content += '
'; + content += '
'; + + content += '
'; + content += ''; + content += ''; + content += '
'; + + content += '
'; + content += ''; + content += ''; + content += '
'; + + content += ' '; + + content += 'streetview'; + content += '
'; + + // im contructing the element so that I can find the button elements i just made + var elem = document.createElement("div"); + elem.innerHTML = content; + + + // now i can look for them + var elems = ["tsave","tremove","ttitle","tinfo","taddress","tclean","tstreetview"].reduce(function (p,c) { + p[c] = findElem (elem, c); + return p; + },{}); + + // check i got them all + if (Object.keys(elems).some (function (d) { + return !elems[d]; + })) throw 'couldnt find all elements:'+JSON.stringify(elems); + + // now attach listeners + google.maps.event.addDomListener(elems.tstreetview, "click", function () { + ns.showStreetView (spot); + }); + + + // now attach listeners + google.maps.event.addDomListener(elems.tsave, "click", function () { + place.title = elems.ttitle.value; + place.info = elems.tinfo.value; + if (place.address !==elems.taddress.value ) { + // need to do a fresh geocode + // if the address changes, then a geocode will happen + // and both the address and the clean address will be updated + // and the marker will move + ns.geoCode(elems.taddress.value) + .then (function (gc) { + place.lat = gc.geometry.location.lat(); + place.lng = gc.geometry.location.lng(); + place["clean address"] = gc.formatted_address; + place.address = elems.taddress.value; + marker.setPosition (place); + elems.tclean.value = place["clean address"]; + ns.resetBounds(); + }) + .catch (function (err) { + errify ("geocoding problem", err + ":cant place:"+ elems.taddress.value); + }); + + } + }); + + google.maps.event.addDomListener( elems.tremove, "click", function () { + // get rid of the spot + ns.removeSpot(spot); + // close the info window & delete the marker + ns.infoWindow.close(); + ns.activeSpot = null; + marker.setMap (null); + // make everything fit again + ns.resetBounds(); + + }); + + + ns.infoWindow.setContent (elem); + return ns.infoWindow; + + function findElem (elem,id) { + // are we there yet? + if (elem.id === id) return elem; + var node = null; + + // look at the children + (elem.hasChildNodes() ? Array.prototype.slice.call (elem.childNodes) : []) + .forEach (function (d) { + if (!node) node = findElem (d , id); + }); + return node; + } + }; + + /** + * @param {object} spot the spot + * @return {string} streetview api link + */ + ns.makeStreetViewLink = function (spot) { + + if (!spot.maps.sv) return ""; + + // we'll use the pano id + var s = spot.maps.sv.pano; + + // and replicate the pov + var p = spot.maps.sv.pov; + + // and the zoom + var z = spot.maps.sv.zoom; + + // convert from zoom to fov + // found that streetview allows the pov zoom to reach 5 , but it ignores it beyond 4, so set max zoom level to 4 + var zoom = Math.min (4 , z); + + // this documentation doesnt quite give a correctly projected result when used in with the SV api + // var fov = 180/ Math.pow (2 , zoom); + // https://developers.google.com/maps/documentation/javascript/streetview#TilingPanoramas + // so im using this, which gives a slightly more accurate result + var fov = Math.atan(Math.pow(2,1-zoom))*360/Math.PI; + + + // replicate the dimensions of the whats been seen on the screen + var st = ns.getComputedStyle ("map"); + var width = npx(st.width); + var height = npx(st.height); + + // the maximum width of a streetview api image 640/640 .. https://developers.google.com/maps/documentation/streetview/ + + // the maximum anything can be is + var maxAnything = 640; + var maxWidth = Math.min (width , maxAnything); + var maxHeight = Math.min (height , maxAnything); + + // resize if necessary .. need to do it twice to retain the aspect ratio + if (width > maxWidth) { + var ratio = maxWidth/width; + width *= ratio; + height *= ratio; + } + + if (height > maxHeight) { + ratio = maxHeight/height; + width *= ratio; + height *= ratio; + } + + var url= 'https://maps.googleapis.com/maps/api/streetview?size=' + + Math.round(width) +'x'+ Math.round(height) +'&pano=' + + s + '&heading=' + p.heading + '&pitch=' + p.pitch + "&fov=" + fov + '&key=' + getMapsApiKey() ; + + return url; + // get rid of px and convert to number + function npx (v){ + return parseFloat(v.replace ("px","")); + } + }; + + /** + * Reverse geocode from a pos + * @param {position} latlng + * @return {Promise} the revere geocoded address + */ + ns.reverseGeoCode = function (latlng) { + var geocoder = new google.maps.Geocoder; + return new Promise (function (resolve, reject) { + geocoder.geocode({'location': latlng}, function(results, status) { + if (status === google.maps.GeocoderStatus.OK && results[1]) { + resolve (results[1].formatted_address); + } + else { + reject (status); + } + }); + }); + }; + + /** + * geocode an address + * @param {string} address an address + * @return {Promise} the geocded address + */ + ns.geoCode = function (address) { + var geocoder = new google.maps.Geocoder(); + return new Promise (function (resolve, reject) { + geocoder.geocode({'address': address}, function(result, status) { + if (status === google.maps.GeocoderStatus.OK) { + resolve (result[0]); + } + else { + reject (status); + } + }); + }); + }; + + /** + * make all the positions from the given data + * @param {[object]} originalData the data from the store + * @return {Promise} the geocoded data + */ + ns.makePositions = function (originalData) { + + // take a copy of the data + var data = JSON.parse(JSON.stringify(originalData)); + + // because we'll update it in line + return Promise.all(data.map(function(d){ + var lat = parseFloat(d.lat); + var lng = parseFloat(d.lng); + // set a default title if none given + d.address = d.address || ""; + d.title = d.title || d.address; + if (isNaN(lat) || isNaN(lng)) { + return ns.geoCode (d.address) + .then (function (gc) { + d.lat = gc.geometry.location.lat(); + d.lng = gc.geometry.location.lng(); + d["clean address"] = gc.formatted_address; + return d; + }); + } + else { + d.lat = lat; + d.lng = lng; + return d; + } + })); + }; + + /** + * fix up the bounds to include all known spots + */ + ns.resetBounds = function () { + // make everything fit + var bounds = new google.maps.LatLngBounds(); + ns.spots.forEach (function (d) { + bounds.extend (d.maps.marker.position); + }); + ns.map.fitBounds(bounds); + }; + + /** + * make an elem visible + */ + ns.showElem = function (item, show) { + document.getElementById (item).classList[show ? 'remove' : 'add'] ("mui--hide"); + }; + + /** + * get computed style + */ + ns.getComputedStyle = function (item) { + return window.getComputedStyle(document.getElementById (item)); + }; + + /** + * toggle sv + */ + ns.togglePano = function() { + ns.pano.setVisible(!ns.pano.getVisible()); + }; + + + /* + * initialize the app and add listeners + * @param {[object]} data the data from the store + */ + ns.init = function (data) { + + ns.makePositions (data.value) + .then (function(results) { + + // initialize the map + + var elem = document.getElementById('map'); + + + ns.map = new google.maps.Map(elem, { + zoom: 5, + mapTypeId: 'terrain' + }); + + + + ns.pano = ns.map.getStreetView(); + ns.pano.setOptions({ + linksControl: true, + enableCloseButton:true, + addressControl:true, + zoomControl:false + }); + + // make only info window, and set its content dynamically + ns.infoWindow = new google.maps.InfoWindow(); + + // when its closed, we'll record that + google.maps.event.addListener(ns.infoWindow,'closeclick',function(){ + ns.activeSpot = null; + }); + + // add the existing data + results.forEach (function (d){ + ns.addMarker (d); + }); + + // make everything fit + ns.resetBounds(); + + // closing the streetview, hide the camera icon + ns.pano.addListener ("closeclick", function (a) { + ns.togglePano(); + ns.showElem("camera",false); + }); + + /** + * this gets called when we move to a different pano + */ + ns.pano.addListener('pano_changed', function() { + ns.activeSpot.maps.sv.pano = ns.pano.getPano(); + + }); + + + + /** + * pov changes + */ + ns.pano.addListener('pov_changed', function() { + var pov = ns.pano.getPov(); + ns.activeSpot.maps.sv.pov.heading = pov.heading; + ns.activeSpot.maps.sv.pov.pitch = pov.pitch; + + }); + + /** + * zoom changes + */ + ns.pano.addListener('zoom_changed', function() { + var zoom = ns.pano.getZoom(); + // this event fires even though there is no more zooming available + // so limit it to 3.1 which seems to be about the highest it goes accurately + + if (zoom > 3.1) { + zoom = 3.1; + } + ns.activeSpot.maps.sv.zoom = zoom; + + + }); + + + // listen for camera click + google.maps.event.addDomListener(document.getElementById("snap"), "click", function () { + ns.activeSpot.place.view = ns.makeStreetViewLink( ns.activeSpot); + errify("snapped",ns.activeSpot.place.title+ " captured"); + }); + + // listen for start again + google.maps.event.addDomListener(document.getElementById("reset"), "click", function () { + // forget everything + ns.activeSpot.maps.sv = null; + ns.showStreetView (ns.activeSpot); + }); + + // This event listener will call addMarker() when the map is clicked. + // can be used to add points + google.maps.event.addListener(ns.map, 'rightclick', function(event) { + var latlng = {lat:event.latLng.lat(), lng:event.latLng.lng()}; + ns.reverseGeoCode(latlng) + .then (function (address){ + var place = { + address:address, + "clean address":address, + lat:parseFloat(latlng.lat), + lng:parseFloat(latlng.lng), + created:new Date().getTime(), + source:"maps", + info:"some info", + title:"title", + view:"" + }; + + ns.addMarker(place); + }) + .catch (function (err) { + errify ('reverse geocode failure', err); + }); + }); + + }) + .catch (function (err) { + errify ("geocoding failure",err); + }); + + return ns; + }; + + // Adds a marker to the map and push to the array. + ns.addMarker = function (place) { + + try { + // make a marker for this place + var marker = new google.maps.Marker({ + position: {lat: place.lat, lng: place.lng}, + map: ns.map, + title: place.title || "", + draggable:true, + animation: google.maps.Animation.DROP + }); + + + // add it to the data + var spot = { + maps:{ + marker:marker, + id:new Date().getTime().toString(32) + Math.random(), + sv:null + }, + place:place + }; + + // if there's a view parameter, decode it and extract the current parameters + if (place.view) { + spot.maps.view = ["pano","pitch","heading","fov"].reduce(function (p,c) { + var match = RegExp('[?&]' + c + '=([^&]*)').exec(place.view); + p[c] = match && decodeURIComponent(match[1].replace(/\+/g, ' ')); + return p; + },{}); + + // now generate an active pano object using those params + + // estimate the zoom + var fov = parseFloat(spot.maps.view.fov); + var zoom = 1-Math.log(Math.tan(fov*Math.PI/360))/Math.log(2); + + spot.maps.sv = { + pano:spot.maps.view.pano, + pov:{ + zoom:zoom, + heading:parseFloat(spot.maps.view.heading), + pitch:parseFloat(spot.maps.view.pitch) + } + }; + } + + // make it clickable + google.maps.event.addListener(spot.maps.marker,"click", function () { + ns.makeInfoWindow(spot).open(ns.map,spot.maps.marker); + }); + + // handle what happens if a marker is dragged + google.maps.event.addListener(spot.maps.marker,"dragend", function () { + // if a drag happens then the clean address is changed, the address is not. + var pos = spot.maps.marker.getPosition(); + + // do a reverse geocode + ns.reverseGeoCode(pos) + .then (function (address) { + spot.place.lat = parseFloat(pos.lat); + spot.place.lng = parseFloat(pos.lng); + spot.place["clean address"]=address; + // make everything fit + ns.resetBounds(); + + // if there's an active spot, then it needs refreshing + ns.makeInfoWindow(spot); + + }) + .catch (function (err) { + errify ('reverse geocode failure', err); + }); + + + }); + + ns.spots.push(spot); + } + catch (err) { + errify("data invalid for "+place.title,err); + } + }; + + +}; + diff --git a/src/yourMapsApi.js b/src/yourMapsApi.js new file mode 100644 index 0000000..ea793d8 --- /dev/null +++ b/src/yourMapsApi.js @@ -0,0 +1,4 @@ + +function getMapsApiKey () { + return 'AxxxxxxxxxxxxxxxxxU'; /// Put your Google maps api key here +}; \ No newline at end of file