diff --git a/examples/annotations/index.jade b/examples/annotations/index.jade index bd78a8a2d5..416b540a4e 100644 --- a/examples/annotations/index.jade +++ b/examples/annotations/index.jade @@ -28,7 +28,9 @@ block append mainContent span.entry-name Sample a.entry-edit(action='edit', title='Edit name and properties') ✎ a.entry-remove(action='remove', title='Delete this annotation') ✖ - // add buttons to copy annotations as geojson and to paste geojson to annotations + .form-group + textarea#geojson(type='textarea', rows=15, autocomplete='off', + autocorrect='off', autocapitalize='off', spellcheck='false') #editdialog.modal.fade .modal-dialog diff --git a/examples/annotations/main.css b/examples/annotations/main.css index c8eeb444fb..c4c93a6201 100644 --- a/examples/annotations/main.css +++ b/examples/annotations/main.css @@ -99,3 +99,7 @@ color: red; font-weight: bold; } +#geojson { + width: 100%; + resize: vertical; +} diff --git a/examples/annotations/main.js b/examples/annotations/main.js index a0bfb32496..a745065858 100644 --- a/examples/annotations/main.js +++ b/examples/annotations/main.js @@ -6,7 +6,7 @@ var annotationDebug = {}; $(function () { 'use strict'; - var layer, fromButtonSelect; + var layer, fromButtonSelect, fromGeojsonUpdate; // get the query parameters and set controls appropriately var query = utils.getQuery(); @@ -16,14 +16,21 @@ $(function () { $('.annotationtype button').removeClass('lastused'); $('.annotationtype button#' + query.lastannotation).addClass('lastused'); } + // You can set the intiial annotations via a query parameter. If the query + // parameter 'save=true' is specified, the query will be updated with the + // geojson. This can become too long for some browsers. + var initialGeoJSON = query.geojson; + // respond to changes in our controls $('#controls').on('change', change_controls); + $('#geojson[type=textarea]').on('input propertychange', change_geojson); $('#controls').on('click', 'a', select_control); $('.annotationtype button').on('click', select_annotation); $('#editdialog').on('submit', edit_update); $('#controls').toggleClass('no-controls', query.controls === 'false'); + // start the map near Fresno unless the query parameters say to do otherwise var map = geo.map({ node: '#map', center: { @@ -33,8 +40,7 @@ $(function () { zoom: query.zoom ? +query.zoom : 8, rotation: query.rotation ? +query.rotation * Math.PI / 180 : 0 }); - // allow some query parameters without controls to specify what map we will - // show + // allow some query parameters to specify what map we will show if (query.map !== 'false') { if (query.map !== 'satellite') { annotationDebug.mapLayer = map.createLayer('osm'); @@ -68,13 +74,18 @@ $(function () { } } + // if we have geojson as a query parameter, populate our annotations + if (initialGeoJSON) { + layer.geojson(initialGeoJSON, true); + } + // expose some internal parameters so you can examine them from the console annotationDebug.map = map; annotationDebug.layer = layer; annotationDebug.query = query; /** - * When the mouse is clicked, switch adding an annotation if appropriate. + * When the mouse is clicked, switch to adding an annotation if appropriate. * * @param {geo.event} evt geojs event. */ @@ -116,9 +127,32 @@ $(function () { value === ctl.attr('placeholder'))) { delete query[param]; } + // update our query parameters, os when you reload the page it is in the + // same state utils.setQuery(query); } + /** + * Handle changes to the geojson. + * + * @param evt jquery evt that triggered this call. + */ + function change_geojson(evt) { + var ctl = $(evt.target), + value = ctl.val(); + // when we update the geojson from the textarea control, raise a flag so we + // (a) ignore bad geojson, and (b) don't replace the user's geojson with + // the auto-generated geojson + fromGeojsonUpdate = true; + var result = layer.geojson(value, 'update'); + if (query.save && result !== undefined) { + var geojson = layer.geojson(); + query.geojson = geojson ? JSON.stringify(geojson) : undefined; + utils.setQuery(query); + } + fromGeojsonUpdate = false; + } + /** * Handle selecting an annotation button. * @@ -209,6 +243,15 @@ $(function () { }); $('#annotationheader').css( 'display', $('#annotationlist .entry').length <= 1 ? 'none' : 'block'); + if (!fromGeojsonUpdate) { + // update the geojson textarea + var geojson = layer.geojson(); + $('#geojson').val(geojson ? JSON.stringify(geojson, undefined, 2) : ''); + if (query.save) { + query.geojson = geojson ? JSON.stringify(geojson) : undefined; + utils.setQuery(query); + } + } } /** @@ -256,12 +299,15 @@ $(function () { dlg.attr('annotation-id', id); dlg.attr('annotation-type', type); $('[option="name"]', dlg).val(annotation.name()); + // populate each control with the current value of the annotation $('.form-group[annotation-types]').each(function () { var ctl = $(this), key = $('[option]', ctl).attr('option'), format = $('[option]', ctl).attr('format'), value; if (!ctl.attr('annotation-types').match(typeMatch)) { + // if a property doesn't exist for the current annotation's type, hide + // the control ctl.hide(); return; } @@ -269,6 +315,7 @@ $(function () { value = opt.style[key]; switch (format) { case 'color': + // always show colors as hex values value = geo.util.convertColorToHex(value); break; } @@ -295,6 +342,7 @@ $(function () { error, newopt = {}; + // validate form values $('.form-group[annotation-types]').each(function () { var ctl = $(this), key = $('[option]', ctl).attr('option'), @@ -333,6 +381,7 @@ $(function () { annotation.options({style: newopt}).draw(); dlg.modal('hide'); + // refresh the annotation list handleAnnotationChange(); } }); diff --git a/examples/common/js/examples.js b/examples/common/js/examples.js index ae01d7e14c..7198b4d257 100644 --- a/examples/common/js/examples.js +++ b/examples/common/js/examples.js @@ -8,7 +8,7 @@ var exampleUtils = { '&').map(function (n) { n = n.split('='); if (n[0]) { - this[decodeURIComponent(n[0])] = decodeURIComponent(n[1]); + this[decodeURIComponent(n[0].replace(/\+/g, '%20'))] = decodeURIComponent(n[1].replace(/\+/g, '%20')); } return this; }.bind({}))[0]; diff --git a/src/annotationLayer.js b/src/annotationLayer.js index e628354e92..36ec8d1583 100644 --- a/src/annotationLayer.js +++ b/src/annotationLayer.js @@ -373,31 +373,52 @@ var annotationLayer = function (args) { * geojson object. If undefined, return the current annotations as * geojson. This may be a JSON string, a javascript object, or a File * object. - * @param {boolean} clear: if true, when adding objects, first remove all - * existing objects. + * @param {boolean} clear: if true, when adding annotations, first remove all + * existing objects. If 'update', update existing annotations and remove + * annotations that no longer exit, If false, update existing + * annotations and leave unchanged annotations. * @param {string|geo.transform} [gcs] undefined to use the interface gcs, * null to use the map gcs, or any other transform. * @param {boolean} includeCrs: if true, include the coordinate system in the * output. - * @return {object} the current annotations as a javascript object that - * can be converted to geojson using JSON.stringify. + * @return {object|number|undefined} if geojson was undefined, the current + * annotations as a javascript object that can be converted to geojson + * using JSON.stringify. If geojson is specified, either the number of + * annotations now present upon success, or undefined if the value in + * geojson was not able to be parsed. */ this.geojson = function (geojson, clear, gcs, includeCrs) { if (geojson !== undefined) { - if (clear) { + var reader = registry.createFileReader('jsonReader', {layer: this}); + if (!reader.canRead(geojson)) { + return; + } + if (clear === true) { this.removeAllAnnotations(true, false); } - var reader = registry.createFileReader('jsonReader', {layer: this}); + if (clear === 'update') { + $.each(this.annotations(), function (idx, annotation) { + annotation.options('updated', false); + }); + } reader.read(geojson, function (features) { $.each(features.slice(), function (feature_idx, feature) { m_this._geojsonFeatureToAnnotation(feature, gcs); m_this.deleteFeature(feature); }); }); + if (clear === 'update') { + $.each(this.annotations(), function (idx, annotation) { + if (annotation.options('updated') === false && + annotation.state() === geo_annotation.state.done) { + m_this.removeAnnotation(annotation, false); + } + }); + } this.modified(); this._update(); this.draw(); - return this; + return m_annotations.length; } geojson = null; var features = []; @@ -521,9 +542,12 @@ var annotationLayer = function (args) { existing = m_this.annotationById(options.annotationId); delete options.annotationId; } - if (existing && existing.type() === type) { + if (existing && existing.type() === type && existing.state() === geo_annotation.state.done) { + /* We could change the state of the existing annotation if it differs + * from done. */ delete options.state; delete options.layer; + options.updated = true; existing.options(options); m_this.geoTrigger(geo_event.annotation.update, { annotation: existing