182 | Displays OpenStreetMap changes based on the Overpass API
183 | Augmented Delta (adiff) query and the
184 | Augmented Diff format.
185 | Tested on Firefox and Chrome; older Internet Explorer and mobile not supported.
186 |
187 | This is an early alpha version and many features are missing, like better relation support, changeset
188 | information, textual list and/or summary of changes and performance optimization. Also expect to find bugs.
189 |
';
381 | }
382 | return infoHtml;
383 | }
384 |
385 | function getDefaultClasses() {
386 | var c = {
387 | key : 'keydefault',
388 | old : 'default',
389 | change : 'default'
390 | };
391 | return c;
392 | }
393 |
394 | function getClasses(oldVal, changeVal, action) {
395 | var c = getDefaultClasses();
396 |
397 | if (_.isUndefined(oldVal)) {
398 | c.key = 'created';
399 | c.old = 'undefined';
400 | c.change = 'created';
401 | } else if (_.isUndefined(changeVal)) {
402 | // default on delete, because tags themselves are unchanged
403 | if (action !== 'delete') {
404 | c.key = 'deleted';
405 | c.old = 'deleted';
406 | }
407 | c.change = 'undefined';
408 | } else if (oldVal !== changeVal) {
409 | c.old = 'modified';
410 | c.change = 'modified';
411 | }
412 |
413 | return c;
414 | }
415 |
416 | function val(value) {
417 | return _.isUndefined(value) ? '' : xssFilters.inHTMLData(value);
418 | }
419 |
420 | return {
421 | setActions : setActions,
422 | getInfoHtml: getInfoHtml,
423 | attachInfoHtmlListeners: attachInfoHtmlListeners,
424 | getOsmType: getOsmType,
425 | formatIsoDateTime: formatIsoDateTime,
426 | isChanged: isChanged
427 | };
428 | })();
429 |
--------------------------------------------------------------------------------
/js/tooltips.js:
--------------------------------------------------------------------------------
1 | var tooltips = (function() {
2 |
3 | // tag id > tooltip text (title attribute)
4 |
5 | var main = {
6 | bbox_button: "click and drag to draw bounding box (optional, else current map view is used), to display and edit activate 'bbox' layer",
7 | /*live_button: "load latest diff and activate minutely refreshing",*/
8 | clear_button: "remove all loaded changes"
9 | };
10 |
11 | var diff = {
12 | fromDatetime: "Enter start date & time (local time) of time range to load (Format: YYYY-MM-DD HH:mm). Defaults to -24h or last visit.",
13 | toDatetime: "Enter end date & time (local time) of time range to load (Format: YYYY-MM-DD HH:mm), or leave empty. Defaults to current time.",
14 | relations: "Check to also load changed relations, only basic support for relations right now.",
15 | load_button: "Load changes in the specified time range"
16 | };
17 |
18 | var player = {
19 | fast_backward_button: "fast backward - starts loading all augmented diffs previous to date & time entered, stop by pushing again, stops after limit of 24h",
20 | backward_button: "backward - load previous augmented diff to date & time entered",
21 | last_visit_button: "last visit - sets date & time to latest diff loaded in last session",
22 | now_button: "now - sets date & time to current time",
23 | datetime: "Enter date & time (local time) of augmented diff to load (Format: YYYY-MM-DD HH:mm), or set with last/now. Defaults to last visit.",
24 | load_button: "load augmented diff of date & time entered",
25 | forward_button: "forward - load next augmented diff to date & time entered",
26 | fast_forward_button: "fast forward - starts loading all augmented diffs after date & time entered, stop by pushing again, stops after limit of 24h",
27 | };
28 |
29 | var status = {
30 | status_countdown: "next refresh countdown - seconds until checking for new augmented diff",
31 | status_time: "base time - OSM base time when the last changes were published (usually lag of 1-2 minutes)",
32 | status_count: "diff count - number of loaded diffs",
33 | status_sequence: "sequence number - number of last loaded augmented diff",
34 | status_total_changes: "total changes - number of changes loaded",
35 | status_new_changes: "new changes - number of changes loaded with last diff",
36 | status_errors: "loading errors - number of too large diffs that were rejected"
37 | };
38 |
39 | /**
40 | * Add texts as tooltip to button and status bars, and also to help dialog.
41 | * Note: title attribute does not work with Firefox for Windows!
42 | */
43 | var setTitlesAndHelp = function(map, name) {
44 | //var html = name + '
';
45 | var html = '
' + name + '
';
46 | for (var id in map) {
47 | var label;
48 | var ele = document.getElementById(id);
49 | ele.setAttribute('title', map[id]);
50 | if (id.substr(0,6) === 'status') {
51 | // for the status bar, set tooltip on labels as well
52 | ele.previousElementSibling.setAttribute('title', map[id]);
53 | label = ele.previousElementSibling.textContent;
54 | } else {
55 | label = ele.textContent;
56 | }
57 | //html += '
' + label + '
' + map[id] + '
';
58 | html += '
' + label + '
' + map[id] + '
';
59 | }
60 | //html += '
';
61 | var eleHelp = document.getElementById('help_tooltips');
62 | eleHelp.innerHTML += html;
63 | };
64 |
65 | setTitlesAndHelp(main, 'Main buttons');
66 | setTitlesAndHelp(diff, 'Time range');
67 | //setTitlesAndHelp(player, 'Player');
68 | //setTitlesAndHelp(status, 'Status bar');
69 | })();
--------------------------------------------------------------------------------
/lib/olex.js:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
2 | * full list of contributors). Published under the 2-clause BSD license.
3 | * See license.txt in the OpenLayers distribution or repository for the
4 | * full text of the license. */
5 |
6 | /**
7 | * @requires OpenLayers/Format/OSM.js
8 | */
9 |
10 | /**
11 | * Class: OpenLayers.Format.OSMMeta
12 | * Extended OSM parser. Adds meta attributes as tags.
13 | * Create a new instance with the constructor.
14 | *
15 | * Inherits from:
16 | * -
17 | */
18 | OpenLayers.Format.OSMMeta = OpenLayers.Class(OpenLayers.Format.OSM, {
19 |
20 | // without id, which is already added as osm_id property to feature
21 | metaAttributes: ['version', 'timestamp', 'uid', 'user', 'changeset'],
22 |
23 | initialize: function(options) {
24 | OpenLayers.Format.OSM.prototype.initialize.apply(this, [options]);
25 | },
26 |
27 | getTags: function(dom_node, interesting_tags) {
28 | var tags = OpenLayers.Format.OSM.prototype.getTags.apply(this, arguments);
29 | var meta = this.getMetaAttributes(dom_node);
30 | tags = OpenLayers.Util.extend(tags, meta);
31 | return tags;
32 | },
33 |
34 | getMetaAttributes: function(dom_node) {
35 | var meta = {}, name;
36 | for (var i = 0; i < this.metaAttributes.length; i++) {
37 | name = this.metaAttributes[i];
38 | meta[name] = dom_node.getAttribute(name);
39 | }
40 | return meta;
41 | },
42 |
43 | CLASS_NAME: "OpenLayers.Format.OSMMeta"
44 | });
45 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
46 | * full list of contributors). Published under the 2-clause BSD license.
47 | * See license.txt in the OpenLayers distribution or repository for the
48 | * full text of the license. */
49 |
50 | /**
51 | * @requires OSMMeta.js
52 | */
53 |
54 | /**
55 | * Class: OpenLayers.Format.OSMExt
56 | * Extended OSM parser. Returns all nodes (including way nodes) and all tags,
57 | * without the overhead of checking interestingTagsExclude, which is ignored.
58 | * Create a new instance with the constructor.
59 | *
60 | * Inherits from:
61 | * -
62 | */
63 | OpenLayers.Format.OSMExt = OpenLayers.Class(OpenLayers.Format.OSMMeta, {
64 |
65 | initialize: function(options) {
66 | OpenLayers.Format.OSMMeta.prototype.initialize.apply(this, [options]);
67 |
68 | // return used nodes (way nodes) as separate entities (check is ignored)
69 | this.checkTags = true;
70 | },
71 |
72 | getTags: function(dom_node, interesting_tags) {
73 | // ignore interesting_tags parameter, pass false to avoid check
74 | var tags = OpenLayers.Format.OSMMeta.prototype.getTags.apply(this, [dom_node, false]);
75 | // all tags are interesting
76 | return interesting_tags ? [tags, true] : tags;
77 | },
78 |
79 | CLASS_NAME: "OpenLayers.Format.OSMExt"
80 | });
81 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
82 | * full list of contributors). Published under the 2-clause BSD license.
83 | * See license.txt in the OpenLayers distribution or repository for the
84 | * full text of the license. */
85 |
86 | /**
87 | * @requires OSMExt.js
88 | */
89 |
90 | /**
91 | * Class: OpenLayers.Format.OSC
92 | * OSC (OSM change file) parser. Create a new instance with the
93 | * constructor.
94 | *
95 | * Inherits from:
96 | * -
97 | */
98 | OpenLayers.Format.OSC = OpenLayers.Class(OpenLayers.Format.OSMExt, {
99 |
100 | initialize: function(options) {
101 | OpenLayers.Format.OSMExt.prototype.initialize.apply(this, [options]);
102 |
103 | this.metaAttributes.push('action');
104 | },
105 |
106 | // osmDoc optional
107 | read: function(doc, osmDoc) {
108 | // copied and modified version of OpenLayers.Format.OSM.read
109 |
110 | if (typeof doc == "string") {
111 | doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
112 | }
113 |
114 | // OSM XML
115 | var osmNodes = {};
116 | if (osmDoc) {
117 | if (typeof osmDoc == "string") {
118 | osmDoc = OpenLayers.Format.XML.prototype.read.apply(this, [osmDoc]);
119 | }
120 | osmNodes = this.getNodes(osmDoc);
121 | }
122 |
123 | // OSC
124 | var nodes = this.getNodes(doc);
125 | var ways = this.getWays(doc);
126 |
127 | // Geoms will contain at least ways.length entries.
128 | var feat_list = new Array(ways.length);
129 |
130 | for (var i = 0; i < ways.length; i++) {
131 | // no fixed length, nodes might be missing
132 | var point_list = [];
133 |
134 | var poly = this.isWayArea(ways[i]) ? 1 : 0;
135 | for (var j = 0; j < ways[i].nodes.length; j++) {
136 | var node = nodes[ways[i].nodes[j]];
137 |
138 | // if not in OSC get referenced node from augmenting file (OSM XML)
139 | if (!node) {
140 | node = osmNodes[ways[i].nodes[j]];
141 | }
142 | if (node) {
143 | var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
144 |
145 | // Since OSM is topological, we stash the node ID internally.
146 | point.osm_id = parseInt(ways[i].nodes[j]);
147 | //point_list[j] = point;
148 | point_list.push(point);
149 |
150 | // We don't display nodes if they're used inside other
151 | // elements.
152 | node.used = true;
153 | } else if (osmDoc) {
154 | console.warn('node "' + ways[i].nodes[j] + '" referenced by way "' + ways[i].id + '" not found');
155 | }
156 | }
157 | if (point_list.length === 0 && ways[i].tags['action'] !== 'delete') {
158 | console.warn('no nodes for way "' + ways[i].id + '" found - way will not appear on map');
159 | }
160 | var geometry = null;
161 | if (poly) {
162 | geometry = new OpenLayers.Geometry.Polygon(
163 | new OpenLayers.Geometry.LinearRing(point_list));
164 | } else {
165 | geometry = new OpenLayers.Geometry.LineString(point_list);
166 | }
167 | if (this.internalProjection && this.externalProjection) {
168 | geometry.transform(this.externalProjection,
169 | this.internalProjection);
170 | }
171 | var feat = new OpenLayers.Feature.Vector(geometry,
172 | ways[i].tags);
173 | feat.osm_id = parseInt(ways[i].id);
174 | feat.fid = "way." + feat.osm_id;
175 | feat_list[i] = feat;
176 | }
177 | for (var node_id in nodes) {
178 | var node = nodes[node_id];
179 | if (!node.used || this.checkTags) {
180 | var tags = null;
181 |
182 | if (this.checkTags) {
183 | var result = this.getTags(node.node, true);
184 | if (node.used && !result[1]) {
185 | continue;
186 | }
187 | tags = result[0];
188 | } else {
189 | tags = this.getTags(node.node);
190 | }
191 |
192 | var feat = new OpenLayers.Feature.Vector(
193 | new OpenLayers.Geometry.Point(node['lon'], node['lat']),
194 | tags);
195 | if (this.internalProjection && this.externalProjection) {
196 | feat.geometry.transform(this.externalProjection,
197 | this.internalProjection);
198 | }
199 | feat.osm_id = parseInt(node_id);
200 | feat.fid = "node." + feat.osm_id;
201 | feat.used = node.used;
202 | feat_list.push(feat);
203 | }
204 | // Memory cleanup
205 | node.node = null;
206 | }
207 | return feat_list;
208 | },
209 |
210 | getMetaAttributes: function(dom_node) {
211 | var meta = OpenLayers.Format.OSMMeta.prototype.getMetaAttributes.apply(this, [dom_node]);
212 | meta['action'] = this.getActionString(dom_node);
213 | return meta;
214 | },
215 |
216 | getActionString: function(dom_node) {
217 | var action = dom_node.parentNode.tagName;
218 | return action;
219 | },
220 |
221 | CLASS_NAME: "OpenLayers.Format.OSC"
222 | });
223 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
224 | * full list of contributors). Published under the 2-clause BSD license.
225 | * See license.txt in the OpenLayers distribution or repository for the
226 | * full text of the license. */
227 |
228 | /**
229 | * @requires OSC.js
230 | */
231 |
232 | /**
233 | * Class: OpenLayers.Format.OSCAugmented
234 | * Augmented OSC (OSM change file) parser. Create a new instance with the
235 | * constructor.
236 | *
237 | * Inherits from:
238 | * -
239 | */
240 | OpenLayers.Format.OSCAugmented = OpenLayers.Class(OpenLayers.Format.OSC, {
241 |
242 | initialize: function(options) {
243 | OpenLayers.Format.OSC.prototype.initialize.apply(this, [options]);
244 | },
245 |
246 | readAugmenting: function(augOscDoc) {
247 | var result = [];
248 |
249 | augOscDoc = this.toDocument(augOscDoc);
250 |
251 | // extract 'augment' node tree and pass to OSM.read (treat as OSM XML doc)
252 | var augmentNode = this.getAugmentNode(augOscDoc);
253 | if (augmentNode) {
254 | result = OpenLayers.Format.OSMExt.prototype.read.apply(this, [augmentNode]);
255 | }
256 |
257 | return result;
258 | },
259 |
260 | /**
261 | * NOTE: modifies the passed document (removes the augment node)!
262 | */
263 | read: function(augOscDoc) {
264 | var result = [];
265 |
266 | augOscDoc = this.toDocument(augOscDoc);
267 |
268 | // extract and delete 'augment' node tree and pass both to OSC.read
269 | // (treat 'augment' node as separate OSM XML doc)
270 | var augmentNode = this.getAugmentNode(augOscDoc);
271 | if (augmentNode) {
272 | augmentNode.parentNode.removeChild(augmentNode);
273 | result = OpenLayers.Format.OSC.prototype.read.apply(this, [augOscDoc, augmentNode]);
274 | } else {
275 | result = OpenLayers.Format.OSC.prototype.read.apply(this, [augOscDoc]);
276 | }
277 |
278 | return result;
279 | },
280 |
281 | isAugmented: function(augOscDoc) {
282 | augOscDoc = this.toDocument(augOscDoc);
283 |
284 | var augment_list = augOscDoc.getElementsByTagName("augment");
285 | return augment_list.length > 0;
286 | },
287 |
288 | toDocument: function(stringOrDoc) {
289 | if (typeof stringOrDoc == "string") {
290 | stringOrDoc = OpenLayers.Format.XML.prototype.read.apply(this, [stringOrDoc]);
291 | }
292 | return stringOrDoc;
293 | },
294 |
295 | getAugmentNode: function(doc) {
296 | var result = null;
297 | var augment_list = doc.getElementsByTagName("augment");
298 | if (augment_list.length === 1) {
299 | result = augment_list[0];
300 | } else {
301 | console.warn('Exactly one "augment" section expected in OSC, found: ' + (augment_list.length));
302 | }
303 | return result;
304 | },
305 |
306 | CLASS_NAME: "OpenLayers.Format.OSCAugmented"
307 | });
308 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
309 | * full list of contributors). Published under the 2-clause BSD license.
310 | * See license.txt in the OpenLayers distribution or repository for the
311 | * full text of the license. */
312 |
313 | /**
314 | * @requires OSC.js
315 | */
316 |
317 | /**
318 | * Class: OpenLayers.Format.OSCAugmented
319 | * Overpass API augmented diff parser. Create a new instance with the
320 | * constructor.
321 | *
322 | * Inherits from:
323 | * -
324 | */
325 | OpenLayers.Format.OSCAugmentedDiff = OpenLayers.Class(OpenLayers.Format.OSC, {
326 |
327 | initialize: function(options) {
328 | OpenLayers.Format.OSC.prototype.initialize.apply(this, [options]);
329 | },
330 |
331 | readAugmenting: function(doc) {
332 | var result = [];
333 |
334 | doc = this.toDocument(doc);
335 |
336 | var osm = doc.createElement("osm");
337 | this.appendChildren(osm, doc.getElementsByTagName("erase"));
338 | this.appendChildren(osm, doc.getElementsByTagName("keep"));
339 |
340 | result = OpenLayers.Format.OSMExt.prototype.read.apply(this, [osm]);
341 |
342 | return result;
343 | },
344 |
345 | read: function(doc) {
346 | var result = [];
347 |
348 | doc = this.toDocument(doc);
349 |
350 | var osm = doc.createElement("osm");
351 | this.appendChildren(osm, doc.getElementsByTagName("erase"));
352 | this.appendChildren(osm, doc.getElementsByTagName("keep"));
353 |
354 | var osc = doc.createElement("osmChange");
355 | this.appendChildren(osc, doc.getElementsByTagName("insert"));
356 |
357 | result = OpenLayers.Format.OSC.prototype.read.apply(this, [osc, osm]);
358 |
359 | return result;
360 | },
361 |
362 | appendChildren: function(node, children) {
363 | var clone, i;
364 | for (i = 0; i < children.length; i++) {
365 | clone = children[i].cloneNode(true);
366 | node.appendChild(clone);
367 | }
368 | },
369 |
370 | isAugmented: function () {
371 | return true;
372 | },
373 |
374 | toDocument: function(stringOrDoc) {
375 | if (typeof stringOrDoc == "string") {
376 | stringOrDoc = OpenLayers.Format.XML.prototype.read.apply(this, [stringOrDoc]);
377 | }
378 | return stringOrDoc;
379 | },
380 |
381 | CLASS_NAME: "OpenLayers.Format.OSCAugmentedDiff"
382 | });
383 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
384 | * full list of contributors). Published under the 2-clause BSD license.
385 | * See license.txt in the OpenLayers distribution or repository for the
386 | * full text of the license. */
387 |
388 | /**
389 | * @requires OSC.js
390 | */
391 |
392 | /**
393 | * Class: OpenLayers.Format.OSCAugmentedDiffIDSorted Overpass API augmented diff
394 | * parser. Create a new instance with the
395 | * constructor.
396 | *
397 | * Inherits from: -
398 | */
399 | OpenLayers.Format.OSCAugmentedDiffIDSorted = OpenLayers.Class(OpenLayers.Format.OSC, {
400 |
401 | initialize : function(options) {
402 | OpenLayers.Format.OSC.prototype.initialize.apply(this, [ options ]);
403 | },
404 |
405 | readAugmenting : function(doc) {
406 | var object;
407 | var old = [];
408 | var change = [];
409 |
410 | doc = this.toDocument(doc);
411 |
412 | var actionElementList = doc.getElementsByTagName("action");
413 | for ( var i = 0; i < actionElementList.length; i++) {
414 | var actionNode = actionElementList[i];
415 | var actionType = actionNode.getAttribute("type");
416 | switch (actionType) {
417 | case 'create':
418 | object = actionNode.firstElementChild;
419 | this.addFeature(change, object, actionNode, actionType);
420 | break;
421 | case 'delete':
422 | case 'modify':
423 | // old
424 | object = actionNode.firstElementChild.firstElementChild;
425 | var oldFeature = this.addFeature(old, object, actionNode, actionType);
426 | // new
427 | object = actionNode.lastElementChild.firstElementChild;
428 | var changeFeature = this.addFeature(change, object, actionNode, actionType);
429 | this.linkFeatures(changeFeature, oldFeature);
430 | break;
431 | case 'info':
432 | // only needed for relations (not handled yet)
433 | break;
434 | default:
435 | console.warn('unhandled action type "' + actionType + '"');
436 | }
437 | }
438 |
439 | return {
440 | old : old,
441 | change : change,
442 | timestamp : this.getTimestamp(doc)
443 | };
444 | },
445 |
446 | getTimestamp: function(doc) {
447 | var timestamp = null;
448 | var metaList = doc.getElementsByTagName("meta");
449 | if (metaList && metaList.length > 0) {
450 | timestamp = metaList[0].getAttribute('osm_base');
451 | }
452 | return timestamp;
453 | },
454 |
455 | read : function(doc) {
456 | var obj = readAugmenting(doc);
457 | return obj.change.concat(obj.old);
458 | },
459 |
460 | addFeature: function(featureList, object, actionNode, actionType) {
461 | var feature = this.parseFeature(object);
462 | if (feature) {
463 | feature.attributes['action'] = this.getActionString(actionNode, actionType);
464 | if (feature.osm_type === 'node') {
465 | var waymember = actionNode.getAttribute("waymember");
466 | feature.used = (waymember && waymember === "yes");
467 | }
468 | featureList.push(feature);
469 | }
470 | return feature;
471 | },
472 |
473 | parseFeature: function(object) {
474 | var feature = null,
475 | tags;
476 | var type = object.tagName.toLowerCase();
477 |
478 | tags = this.getTags(object);
479 |
480 | var geometry = this.parseGeometry[type].apply(this, [object, tags]);
481 | if (geometry) {
482 | if (this.internalProjection && this.externalProjection) {
483 | geometry.transform(this.externalProjection,
484 | this.internalProjection);
485 | }
486 | feature = new OpenLayers.Feature.Vector(geometry, tags);
487 |
488 | feature.osm_id = parseInt(object.getAttribute("id"));
489 | feature.osm_type = type;
490 | feature.fid = type + "." + feature.osm_id;
491 | }
492 |
493 | return feature;
494 | },
495 |
496 | getActionString: function(actionNode, actionType) {
497 | var actionString = actionType;
498 | var geometryAttr;
499 | if (actionType === 'modify') {
500 | geometryAttr = actionNode.getAttribute("geometry");
501 | if (geometryAttr && geometryAttr === "changed") {
502 | actionString = 'modify:geometry';
503 | }
504 | }
505 | return actionString;
506 | },
507 |
508 | /**
509 | * Property: parseGeometry
510 | * Properties of this object are the functions that parse geometries based
511 | * on their type.
512 | */
513 | parseGeometry: {
514 | node: function(objectNode, tags) {
515 | var geometry = new OpenLayers.Geometry.Point(
516 | objectNode.getAttribute("lon"),
517 | objectNode.getAttribute("lat"));
518 | return geometry;
519 | },
520 |
521 | way: function(object, tags) {
522 | var geometry, node, point;
523 | var nodeList = object.getElementsByTagName("nd");
524 |
525 | // We know the minimal of this one ahead of time. (Could be -1
526 | // due to areas/polygons)
527 | var pointList = new Array(nodeList.length);
528 | for (var j = 0; j < nodeList.length; j++) {
529 | node = nodeList[j];
530 |
531 | point = new OpenLayers.Geometry.Point(
532 | node.getAttribute("lon"),
533 | node.getAttribute("lat"));
534 |
535 | // Since OSM is topological, we stash the node ID internally.
536 | point.osm_id = parseInt(node.getAttribute("ref"));
537 | pointList[j] = point;
538 | }
539 |
540 | if (this.isWayArea(pointList, tags)) {
541 | geometry = new OpenLayers.Geometry.Polygon(
542 | new OpenLayers.Geometry.LinearRing(pointList));
543 | } else {
544 | geometry = new OpenLayers.Geometry.LineString(pointList);
545 | }
546 | return geometry;
547 | },
548 |
549 | relation: function(objectNode) {
550 | // not handled yet
551 | },
552 | },
553 |
554 | linkFeatures: function(changeFeature, oldFeature) {
555 | if (changeFeature) {
556 | changeFeature.oldFeature = oldFeature;
557 | }
558 | if (oldFeature) {
559 | oldFeature.changeFeature = changeFeature;
560 | }
561 | },
562 |
563 | // use original, not super method, because action is determined from
564 | // action not object node
565 | getMetaAttributes: OpenLayers.Format.OSMMeta.prototype.getMetaAttributes,
566 |
567 | /**
568 | * Method: isWayArea
569 | * Check whether the tags and geometry indicate something is an area.
570 | *
571 | * Parameters:
572 | * pointList - {Array()} Way nodes
573 | * tags - {Object} Way tags
574 | *
575 | * Returns:
576 | * {Boolean}
577 | */
578 | isWayArea: function(pointList, tags) {
579 | var poly_shaped = false;
580 | var poly_tags = false;
581 |
582 | if (pointList.length > 2
583 | && pointList[0].osm_id === pointList[pointList.length - 1].osm_id) {
584 | poly_shaped = true;
585 | }
586 |
587 | for(var key in tags) {
588 | if (this.areaTags[key]) {
589 | poly_tags = true;
590 | break;
591 | }
592 | }
593 |
594 | return poly_shaped && poly_tags;
595 | },
596 |
597 | appendChildren : function(node, children) {
598 | var clone, i;
599 | for (i = 0; i < children.length; i++) {
600 | clone = children[i].cloneNode(true);
601 | node.appendChild(clone);
602 | }
603 | },
604 |
605 | isAugmented : function() {
606 | return true;
607 | },
608 |
609 | toDocument : function(stringOrDoc) {
610 | if (typeof stringOrDoc == "string") {
611 | stringOrDoc = OpenLayers.Format.XML.prototype.read.apply(this, [ stringOrDoc ]);
612 | }
613 | return stringOrDoc;
614 | },
615 |
616 | CLASS_NAME : "OpenLayers.Format.OSCAugmentedDiffIDSorted"
617 | });
618 | /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for
619 | * full list of contributors). Published under the 2-clause BSD license.
620 | * See license.txt in the OpenLayers distribution or repository for the
621 | * full text of the license. */
622 |
623 | /**
624 | * @requires OpenLayers/Format/XML.js
625 | * @requires OpenLayers/Feature/Vector.js
626 | * @requires OpenLayers/Geometry/Point.js
627 | * @requires OpenLayers/Geometry/LineString.js
628 | * @requires OpenLayers/Geometry/Polygon.js
629 | * @requires OpenLayers/Projection.js
630 | */
631 |
632 | /**
633 | * Class: OpenLayers.Format.OSM
634 | * OSM parser. Create a new instance with the
635 | * constructor.
636 | *
637 | * Inherits from:
638 | * -
639 | */
640 | OpenLayers.Format.OSMChangeset = OpenLayers.Class(OpenLayers.Format.XML, {
641 |
642 | metaAttributes: ['user', 'uid', 'created_at', 'closed_at', 'open', 'min_lat', 'min_lon', 'max_lat', 'max_lon'],
643 |
644 | /**
645 | * Constructor: OpenLayers.Format.OSM
646 | * Create a new parser for OSM.
647 | *
648 | * Parameters:
649 | * options - {Object} An optional object whose properties will be set on
650 | * this instance.
651 | */
652 | initialize: function(options) {
653 |
654 | // OSM coordinates are always in longlat WGS84
655 | this.externalProjection = new OpenLayers.Projection("EPSG:4326");
656 |
657 | OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
658 | },
659 |
660 | /**
661 | * APIMethod: read
662 | * Return changeset from a OSM changeset doc
663 |
664 | * Parameters:
665 | * doc - {Element}
666 | *
667 | * Returns:
668 | * Array({})
669 | */
670 | read: function(doc) {
671 | if (typeof doc == "string") {
672 | doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
673 | }
674 |
675 | var feat_list = [];
676 | var changesetNode = null;
677 | var nodeList = doc.getElementsByTagName("changeset");
678 | if (nodeList.length > 0) {
679 | changesetNode = nodeList[0];
680 | var tags = this.getTags(changesetNode);
681 |
682 | // left, bottom, right, top
683 | var bounds = new OpenLayers.Bounds(tags.min_lon, tags.min_lat, tags.max_lon, tags.max_lat);
684 | var geometry = bounds.toGeometry();
685 | if (this.internalProjection && this.externalProjection) {
686 | geometry.transform(this.externalProjection,
687 | this.internalProjection);
688 | }
689 | var feat = new OpenLayers.Feature.Vector(geometry, tags);
690 | feat.osm_id = parseInt(changesetNode.getAttribute("id"));
691 | feat.fid = "changeset." + feat.osm_id;
692 | feat_list.push(feat);
693 | }
694 |
695 | return feat_list;
696 | },
697 |
698 | getTags: OpenLayers.Format.OSMMeta.prototype.getTags,
699 |
700 | getMetaAttributes: OpenLayers.Format.OSMMeta.prototype.getMetaAttributes,
701 |
702 | CLASS_NAME: "OpenLayers.Format.OSMChangeset"
703 | });
704 | OpenLayers.Control.LayerSwitcherBorder = OpenLayers.Class(OpenLayers.Control.LayerSwitcher, {
705 |
706 | borderDiv: null,
707 |
708 | initialize: function(options) {
709 | OpenLayers.Control.LayerSwitcher.prototype.initialize.apply(this, arguments);
710 | },
711 |
712 | draw: function() {
713 | this.borderDiv = OpenLayers.Control.prototype.draw.apply(this);
714 | this.div = null;
715 | /*
716 | this.borderDiv = document.createElement("div");
717 | //OpenLayers.Element.addClass(this.borderDiv, "border");
718 | OpenLayers.Element.addClass(this.borderDiv, this.displayClass);
719 | if (!this.allowSelection) {
720 | this.borderDiv.className += " olControlNoSelect";
721 | this.borderDiv.setAttribute("unselectable", "on", 0);
722 | this.borderDiv.onselectstart = OpenLayers.Function.False;
723 | }
724 | */
725 |
726 | OpenLayers.Control.LayerSwitcher.prototype.draw.apply(this);
727 |
728 | //this.div.style.width = this.borderDiv.style.width;
729 | //this.borderDiv.style.width = "auto";
730 | this.div.className = "layerSwitcherDiv";
731 | this.div.style.position = "";
732 | //OpenLayers.Element.addClass(this.div, "layerSwitcherDiv");
733 | //this.borderDiv.id = this.div.id;
734 | this.div.id = this.div.id + "_layerSwitcherDiv";
735 |
736 | this.maximizeDiv.style.position = "";
737 |
738 | this.borderDiv.appendChild(this.div);
739 |
740 | // OpenLayers.Util.modifyAlphaImageDiv(this.maximizeDiv, null, null, {w: 22, h: 22});
741 |
742 | return this.borderDiv;
743 | },
744 |
745 | maximizeControl: function(e) {
746 |
747 | // set the div's width and height to empty values, so
748 | // the div dimensions can be controlled by CSS
749 | // this.div.style.width = "";
750 | // this.div.style.height = "";
751 | this.layersDiv.style.display = "";
752 |
753 | this.showControls(false);
754 |
755 | if (e != null) {
756 | OpenLayers.Event.stop(e);
757 | }
758 | },
759 |
760 | minimizeControl: function(e) {
761 |
762 | // to minimize the control we set its div's width
763 | // and height to 0px, we cannot just set "display"
764 | // to "none" because it would hide the maximize
765 | // div
766 | // this.div.style.width = "0px";
767 | // this.div.style.height = "0px";
768 | this.layersDiv.style.display = "none";
769 |
770 | this.showControls(true);
771 |
772 | if (e != null) {
773 | OpenLayers.Event.stop(e);
774 | }
775 | }
776 |
777 | // CLASS_NAME: keep parent name because CSS classes are named after this
778 | });
779 | /**
780 | * Reads files using HTML5 file API
781 | *
782 | * derived from http://www.html5rocks.com/en/tutorials/file/dndfiles/
783 | */
784 | function FileReaderControl(onLoadCallback) {
785 | this.onLoadCallback = onLoadCallback;
786 |
787 | // {urlRegex:, handler:} objects
788 | this.urlHandlers = [];
789 | }
790 |
791 | FileReaderControl.prototype.addUrlHandler = function(urlRegex, handler) {
792 | this.urlHandlers.push({urlRegex: urlRegex, handler: handler});
793 | };
794 |
795 | FileReaderControl.prototype.activate = function() {
796 | if (window.File && window.FileReader && window.FileList) {
797 | document.getElementById('fileinput').addEventListener('change', _.bind(this.handleFileSelect, this), false);
798 |
799 | var dropZone = document.getElementById('map_div');
800 | dropZone.addEventListener('dragover', this.handleDragOver, false);
801 | dropZone.addEventListener('drop', _.bind(this.handleFileSelect, this), false);
802 | } else {
803 | // File API not supported
804 | document.getElementById('fileinput').disabled = true;
805 | console.warn('Browser does not support the HTML5 File API!');
806 | }
807 | };
808 |
809 | FileReaderControl.prototype.handleFileSelect = function(evt) {
810 | var files, file;
811 |
812 | evt.stopPropagation();
813 | evt.preventDefault();
814 |
815 | // FileList from file input or drag'n'drop
816 | files = evt.target.files || evt.dataTransfer.files;
817 | if (files.length > 0) {
818 | file = files[0];
819 | console.log('handleFileSelect: ' + file.name);
820 |
821 | if (file.type === 'application/xml' || file.type === 'text/xml' || !file.type) {
822 | var fileReader = new FileReader();
823 | var handleLoad = _.bind(this.onLoadCallback, this);
824 | fileReader.onload = function(evt) {
825 | var text = evt.target.result;
826 | handleLoad(text, file.name);
827 | };
828 | fileReader.onerror = function(evt) {
829 | console.error('Error: ' + evt.target.error.code);
830 | };
831 | fileReader.readAsText(file);
832 | } else {
833 | console.error("File type '" + file.type + "' not recognized as XML for " + file.name);
834 | }
835 | } else if (evt.dataTransfer ) {
836 | var url = evt.dataTransfer.getData("URL");
837 | if (url) {
838 | var handled = false;
839 | for (var i = 0; i < this.urlHandlers.length; i++) {
840 | var obj = this.urlHandlers[i];
841 | if (obj.urlRegex.test(url)) {
842 | obj.handler(url);
843 | handled = true;
844 | break;
845 | }
846 | }
847 | if (!handled) {
848 | console.warn("no handler found for url: " + url);
849 | }
850 | } else {
851 | console.warn("unhandled event dataTransfer: " + evt.dataTransfer);
852 | }
853 | } else {
854 | console.warn("unhandled event: " + evt);
855 | }
856 | };
857 |
858 | FileReaderControl.prototype.handleDragOver = function(evt) {
859 | evt.stopPropagation();
860 | evt.preventDefault();
861 | evt.dataTransfer.dropEffect = 'copy';
862 | };
863 | OpenLayers.Control.HoverAndSelectFeature = OpenLayers.Class(OpenLayers.Control.SelectFeature, {
864 | initialize : function(layers, options) {
865 | this.hover = true;
866 | OpenLayers.Control.SelectFeature.prototype.initialize.apply(this, [ layers, options ]);
867 |
868 | // allow map panning while feature hovered or selected
869 | this.handlers['feature'].stopDown = false;
870 | this.handlers['feature'].stopUp = false;
871 | },
872 |
873 | clickFeature : function(feature) {
874 | if (this.hover) {
875 | this.hover = false;
876 | if (!this.highlightOnly) {
877 | // feature already selected by hover, unselect before calling super,
878 | // which is done to allow select handler to distinguish between hover and click
879 | this.unselect(feature);
880 | }
881 | }
882 | OpenLayers.Control.SelectFeature.prototype.clickFeature.apply(this, [ feature ]);
883 | },
884 |
885 | clickoutFeature : function(feature) {
886 | OpenLayers.Control.SelectFeature.prototype.clickoutFeature.apply(this, [ feature ]);
887 | this.hover = true;
888 | },
889 |
890 | CLASS_NAME : "OpenLayers.Control.HoverAndSelectFeature"
891 | });
892 | var bbox = (function() {
893 |
894 | var drawFeature, transform;
895 | var map, bboxLayer;
896 |
897 | /**
898 | * update, activate, deactivate
899 | */
900 | var callbacks;
901 |
902 | var style = {
903 | "default" : {
904 | fillColor : "#FFD119",
905 | fillOpacity : 0.1,
906 | strokeWidth : 2,
907 | strokeColor : "#333",
908 | strokeDashstyle : "solid"
909 | },
910 | "select" : {
911 | fillOpacity : 0.2,
912 | strokeWidth : 2.5,
913 | },
914 | "temporary" : {
915 | fillColor : "#FFD119",
916 | fillOpacity : 0.1,
917 | strokeDashstyle : "longdash"
918 | },
919 | "transform" : {
920 | display : "${getDisplay}",
921 | cursor : "${role}",
922 | pointRadius : 6,
923 | fillColor : "rgb(158, 158, 158)",
924 | fillOpacity : 1,
925 | strokeColor : "#333",
926 | strokeWidth : 2,
927 | strokeOpacity : 1
928 | }
929 | };
930 |
931 | function createStyleMap() {
932 |
933 | var styleMap = new OpenLayers.StyleMap({
934 | //"default" : new OpenLayers.Style(defaultStyle),
935 | "default" : new OpenLayers.Style(style["default"]),
936 | "select" : new OpenLayers.Style(style["select"]),
937 | "temporary" : new OpenLayers.Style(style["temporary"]),
938 | // render intent for TransformFeature control
939 | "transform" : new OpenLayers.Style(style["transform"], {
940 | context : {
941 | getDisplay : function(feature) {
942 | // Hide transform box, as it's styling is limited because of underlying bbox feature.
943 | // Instead, the render intent of the bbox feature is assigned separately.
944 | return feature.geometry.CLASS_NAME === "OpenLayers.Geometry.LineString" ? "none" : "";
945 | },
946 | }
947 | })
948 | });
949 | /* debug
950 | var orig = OpenLayers.StyleMap.prototype.createSymbolizer;
951 | OpenLayers.StyleMap.prototype.createSymbolizer = function(feature, intent) {
952 | var ret = orig.apply(this, arguments);
953 | console.log(intent + '( ' + this.extendDefault + '): ' + JSON.stringify(ret));
954 | return ret;
955 | };
956 | */
957 |
958 | return styleMap;
959 | }
960 |
961 | function featureInsert(feature) {
962 | drawFeatureDeactivate();
963 | callbacks.update(getBBox(feature));
964 | }
965 |
966 | function onTransformComplete(evt) {
967 | callbacks.update(getBBox(evt.feature));
968 | }
969 |
970 | function drawFeatureActivate() {
971 | drawFeature.activate();
972 | if (transform.active) {
973 | transform.deactivate();
974 | }
975 | bboxLayer.destroyFeatures();
976 |
977 | // crosshair cursor
978 | OpenLayers.Element.addClass(map.viewPortDiv, "olDrawBox");
979 |
980 | callbacks.activate();
981 | }
982 |
983 | function drawFeatureDeactivate() {
984 | drawFeature.deactivate();
985 |
986 | // default cursor (remove crosshair cursor)
987 | OpenLayers.Element.removeClass(map.viewPortDiv, "olDrawBox");
988 |
989 | callbacks.deactivate();
990 | }
991 |
992 | function switchActive() {
993 | if (!drawFeature.active) {
994 | drawFeatureActivate();
995 | } else {
996 | drawFeatureDeactivate();
997 | }
998 | }
999 |
1000 | function addControls(pMap, pBboxLayer, pCallbacks) {
1001 |
1002 | callbacks = pCallbacks;
1003 | bboxLayer = pBboxLayer;
1004 | map = pMap;
1005 |
1006 | // draw control
1007 | /* TODO: use feature label or popup to update coordinates while drawing
1008 | var onMove = function(geometry) {
1009 | updateInfo(new OpenLayers.Feature.Vector(geometry));
1010 | };
1011 | */
1012 | var polyOptions = {
1013 | irregular : true,
1014 | // allow dragging beyond map viewport
1015 | documentDrag : true
1016 | };
1017 | drawFeature = new OpenLayers.Control.DrawFeature(bboxLayer, OpenLayers.Handler.RegularPolygon, {
1018 | handlerOptions : polyOptions
1019 | /*
1020 | ,callbacks : {
1021 | move : onMove
1022 | }
1023 | */
1024 | });
1025 | drawFeature.featureAdded = featureInsert;
1026 | map.addControl(drawFeature);
1027 |
1028 | // feature edit control (move and resize), activated by select control
1029 | transform = new OpenLayers.Control.TransformFeature(bboxLayer, {
1030 | renderIntent : "transform",
1031 | rotate : false,
1032 | irregular : true
1033 | });
1034 | transform.events.register("transformcomplete", transform, onTransformComplete);
1035 | map.addControl(transform);
1036 |
1037 | // select control
1038 | // - highlight feature on hover to indicate that it is clickable
1039 | // - activate editing on click (select), deactivate editing on click on map (unselect)
1040 | var select = new OpenLayers.Control.HoverAndSelectFeature(bboxLayer, {
1041 | hover : true,
1042 | highlightOnly : true,
1043 | onSelect : function(feature) {
1044 | select.unhighlight(feature);
1045 | transform.setFeature(feature);
1046 | feature.renderIntent = "temporary";
1047 | bboxLayer.drawFeature(feature);
1048 | },
1049 | onUnselect : function(feature) {
1050 | transform.unsetFeature();
1051 | feature.renderIntent = "default";
1052 | bboxLayer.drawFeature(feature);
1053 | }
1054 | });
1055 |
1056 | map.addControl(select);
1057 | select.activate();
1058 | }
1059 |
1060 | function getBBox(feature) {
1061 | return roundAndTransform(feature.geometry.getBounds());
1062 | }
1063 |
1064 | function roundAndTransform(aBounds) {
1065 | var bounds = aBounds.clone().transform(map.getProjectionObject(), map.displayProjection);
1066 |
1067 | var decimals = Math.floor(map.getZoom() / 3);
1068 | var multiplier = Math.pow(10, decimals);
1069 |
1070 | // custom float.toFixed function that rounds to integer when .0
1071 | // see OpenLayers.Bounds.toBBOX
1072 | var toFixed = function(num) {
1073 | return Math.round(num * multiplier) / multiplier;
1074 | };
1075 |
1076 | // (left, bottom, right, top)
1077 | var box = new OpenLayers.Bounds(
1078 | toFixed(bounds.left),
1079 | toFixed(bounds.bottom),
1080 | toFixed(bounds.right),
1081 | toFixed(bounds.top)
1082 | );
1083 |
1084 | return box;
1085 | }
1086 |
1087 | function addBBoxFromViewPort() {
1088 | var bounds = map.getExtent();
1089 | bboxLayer.addFeatures([new OpenLayers.Feature.Vector(bounds.toGeometry())]);
1090 |
1091 | return roundAndTransform(bounds);
1092 | }
1093 |
1094 | return {
1095 | style: style,
1096 | createStyleMap : createStyleMap,
1097 | addControls : addControls,
1098 | switchActive : switchActive,
1099 | addBBoxFromViewPort : addBBoxFromViewPort
1100 | };
1101 | })();(function() {
1102 | /*
1103 | * Patches OpenLayers.Request.issue.
1104 | * Adds config option "disableXRequestedWith" to disable setting X-Requested-With header.
1105 | * (Error in Chrome: "Request header field X-Requested-With is not allowed by Access-Control-Allow-Headers")
1106 | * see https://github.com/openlayers/openlayers/issues/188
1107 | */
1108 |
1109 | var funcOldStr = OpenLayers.Request.issue.toString();
1110 | var replacement = "customRequestedWithHeader === false && !(config.disableXRequestedWith === true)";
1111 |
1112 | // support both compressed and uncompressed
1113 | var funcNewStr = funcOldStr.replace("customRequestedWithHeader===false", replacement);
1114 | funcNewStr = funcNewStr.replace("customRequestedWithHeader === false", replacement);
1115 |
1116 | eval('OpenLayers.Request.issue = ' + funcNewStr);
1117 | console.warn('patched OpenLayers.Request.issue');
1118 | //console.debug(OpenLayers.Request.issue);
1119 | })();
1120 |
--------------------------------------------------------------------------------
/lib/overpass.js:
--------------------------------------------------------------------------------
1 | // https://github.com/drolbr/Overpass-API/blob/master/html/overpass.js
2 | // with modifications
3 |
4 | OpenLayers.Control.Click = OpenLayers.Class(OpenLayers.Control, {
5 |
6 | genericUrl: "",
7 | tolerance: 0.0,
8 | map: "",
9 |
10 | defaultHandlerOptions: {
11 | 'single': true,
12 | 'double': false,
13 | 'pixelTolerance': 0,
14 | 'stopSingle': false,
15 | 'stopDouble': false
16 | },
17 |
18 | initialize: function(url, tolerance, map) {
19 | this.genericUrl = url;
20 | this.tolerance = tolerance;
21 | this.map = map;
22 |
23 | this.handlerOptions = OpenLayers.Util.extend(
24 | {}, this.defaultHandlerOptions
25 | );
26 | OpenLayers.Control.prototype.initialize.apply(
27 | this, arguments
28 | );
29 | this.handler = new OpenLayers.Handler.Click(
30 | this, {
31 | 'click': this.trigger
32 | }, this.handlerOptions
33 | );
34 | },
35 |
36 | trigger: function(evt) {
37 | var lonlat = map.getLonLatFromPixel(evt.xy)
38 | .transform(new OpenLayers.Projection("EPSG:900913"),
39 | new OpenLayers.Projection("EPSG:4326"));
40 |
41 | var popup = new OpenLayers.Popup("location_info",
42 | new OpenLayers.LonLat(lonlat.lon, lonlat.lat)
43 | .transform(new OpenLayers.Projection("EPSG:4326"), new OpenLayers.Projection("EPSG:900913")),
44 | null,
45 | "Loading ...",
46 | true);
47 |
48 | popup.contentSize = new OpenLayers.Size(400, 400);
49 | popup.closeOnMove = true;
50 | map.addPopup(popup);
51 |
52 | var rel_tolerance = this.tolerance * map.getScale();
53 | if (rel_tolerance > 0.01)
54 | rel_tolerance = 0.01;
55 |
56 | var request = OpenLayers.Request.GET({
57 | url: this.genericUrl + "&bbox="
58 | + (lonlat.lon - rel_tolerance) + "," + (lonlat.lat - rel_tolerance) + ","
59 | + (lonlat.lon + rel_tolerance) + "," + (lonlat.lat + rel_tolerance),
60 | async: false
61 | });
62 |
63 | map.removePopup(popup);
64 | popup.contentHTML = request.responseText;
65 | map.addPopup(popup);
66 | }
67 |
68 | });
69 |
70 | // ----------------------------------------------------------------------------
71 |
72 | function setStatusText(text)
73 | {
74 | var html_node = document.getElementById("statusline");
75 | if (html_node != null)
76 | {
77 | var div = html_node.firstChild;
78 | div.deleteData(0, div.nodeValue.length);
79 | div.appendData(text);
80 | }
81 | }
82 |
83 | var zoom_valid = true;
84 | var load_counter = 0;
85 |
86 | function make_features_added_closure(layer) {
87 | var layer_ = layer;
88 | return function(evt) {
89 | setStatusText("Displaying " + layer_.features.length + " features ...");
90 | };
91 | }
92 |
93 | ZoomLimitedBBOXStrategy = OpenLayers.Class(OpenLayers.Strategy.BBOX, {
94 |
95 | zoom_data_limit: 13,
96 |
97 | initialize: function(zoom_data_limit) {
98 | this.zoom_data_limit = zoom_data_limit;
99 | },
100 |
101 | update: function(options) {
102 | this.ratio = this.layer.ratio;
103 | var mapBounds = this.getMapBounds();
104 | if (this.layer && this.layer.map && this.layer.map.getZoom() < this.zoom_data_limit) {
105 | setStatusText("Please zoom in to view data.");
106 | zoom_valid = false;
107 |
108 | this.bounds = null;
109 | }
110 | else if (mapBounds !== null && ((options && options.force) ||
111 | this.invalidBounds(mapBounds))) {
112 | ++load_counter;
113 | setStatusText("Loading data ...");
114 | zoom_valid = true;
115 |
116 | this.calculateBounds(mapBounds);
117 | this.resolution = this.layer.map.getResolution();
118 | this.triggerRead(options);
119 | }
120 | },
121 |
122 | CLASS_NAME: "ZoomLimitedBBOXStrategy"
123 | });
124 |
125 | OSMTimeoutFormat = OpenLayers.Class(OpenLayers.Format.OSM, {
126 |
127 | initialize: function(strategy) {
128 | this.strategy = strategy;
129 | },
130 |
131 | read: function(doc)
132 | {
133 | if (typeof doc == "string") {
134 | doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
135 | }
136 |
137 | var feat_list = OpenLayers.Format.OSM.prototype.read.apply(this, [doc]);
138 |
139 | if (this.strategy)
140 | {
141 | var node_list = doc.getElementsByTagName("remark");
142 | if (node_list.length > 0)
143 | {
144 | setStatusText("Please zoom in to view data.");
145 | this.strategy.bounds = null;
146 | }
147 | }
148 |
149 | return feat_list;
150 | },
151 |
152 | strategy: null,
153 |
154 | CLASS_NAME: "OSMTimeoutFormat"
155 | });
156 |
157 | //-----------------------------------------------------------------------------
158 |
159 | var OSMDiffFormat = OpenLayers.Class(OpenLayers.Format.OSMMeta, {
160 |
161 | extent: {},
162 |
163 | setStatus: function(status) {},
164 |
165 | pushTextualResult: function(feature) {},
166 |
167 | initialize: function(options)
168 | {
169 | OpenLayers.Format.OSMMeta.prototype.initialize.apply(this, [ options ]);
170 |
171 | if (options && options.extent)
172 | this.extent = options.extent;
173 | if (options && options.setStatus)
174 | this.setStatus = options.setStatus;
175 | if (options && options.pushTextualResult)
176 | this.pushTextualResult = options.pushTextualResult;
177 | },
178 |
179 | read: function(doc)
180 | {
181 | this.setStatus("Processing data");
182 |
183 | if (typeof doc == "string")
184 | doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
185 |
186 | var feat_list = [];
187 |
188 | var relations = this.getRelations(doc);
189 | for (var relation_id in relations)
190 | feat_list.push(relations[relation_id]);
191 |
192 | var ways = this.getWays(doc);
193 | for (var way_id in ways)
194 | feat_list.push(ways[way_id]);
195 |
196 | var nodes = this.getNodes(doc);
197 | for (var node_id in nodes)
198 | feat_list.push(nodes[node_id]);
199 |
200 | this.setStatus("Ready");
201 | return feat_list;
202 | },
203 |
204 | isInExtent: function(node)
205 | {
206 | if (!(node.getAttribute("lon") && node.getAttribute("lat")))
207 | if (node.nodeName === "node")
208 | return true; // keep deleted nodes for popup info
209 | else
210 | return false; // filter out way/relation node refs without geometry (outside bbox, because of geom(bbox))
211 |
212 | if (!this.extent)
213 | return true;
214 |
215 | var geom = new OpenLayers.Geometry.Point(
216 | node.getAttribute("lon"), node.getAttribute("lat"));
217 | return (geom.x <= this.extent.right && geom.x >= this.extent.left &&
218 | geom.y <= this.extent.top && geom.y >= this.extent.bottom);
219 | },
220 |
221 | stateOf: function(element)
222 | {
223 | var state = {};
224 | if (element.parentNode.nodeName == "old")
225 | state = { state: "old" };
226 | else
227 | state = { state: "new" };
228 |
229 | return state;
230 | },
231 |
232 | getAction: function(element)
233 | {
234 | var actionElement = element.parentNode;
235 |
236 | if (actionElement.nodeName !== 'action') {
237 | actionElement = actionElement.parentNode;
238 | }
239 | return actionElement.getAttribute('type');
240 | },
241 |
242 | pointGeomFromNode: function(node)
243 | {
244 | var geom = new OpenLayers.Geometry.Point(
245 | node.getAttribute("lon"), node.getAttribute("lat"));
246 | if (this.internalProjection && this.externalProjection)
247 | geom.transform(this.externalProjection, this.internalProjection);
248 | geom.ref = node.getAttribute('ref');
249 | return geom;
250 | },
251 |
252 | pointListFromWay: function(node_list)
253 | {
254 | var lower = 0;
255 | while (lower < node_list.length && !this.isInExtent(node_list[lower]))
256 | ++lower;
257 | if (lower > 0 && node_list[lower-1].getAttribute("lat") && node_list[lower-1].getAttribute("lon"))
258 | --lower;
259 |
260 | var upper = node_list.length;
261 | while (upper > 0 && !this.isInExtent(node_list[upper-1]))
262 | --upper;
263 | if (upper < node_list.length
264 | && node_list[upper].getAttribute("lat") && node_list[upper].getAttribute("lon"))
265 | ++upper;
266 |
267 | if (upper < lower)
268 | return new Array();
269 |
270 | var point_list = new Array(upper - lower);
271 | var pos = 0;
272 | for (var j = lower; j < upper; ++j)
273 | {
274 | if (node_list[j].getAttribute("lat") && node_list[j].getAttribute("lon"))
275 | point_list[pos++] = this.pointGeomFromNode(node_list[j]);
276 | }
277 | point_list = point_list.slice(0, pos);
278 |
279 | return point_list;
280 | },
281 |
282 | getNodes: function(doc)
283 | {
284 | var node_list = doc.getElementsByTagName("node");
285 | var usedNodesList = doc.querySelectorAll('nd[ref]');
286 | var usedNodes = {};
287 | var nodes = {};
288 | var i;
289 |
290 | for (i = 0; i < usedNodesList.length; ++i) {
291 | usedNodes[usedNodesList[i].getAttribute('ref')] = true;
292 | }
293 |
294 | for (i = 0; i < node_list.length; ++i)
295 | {
296 | var node = node_list[i];
297 | var id = node.getAttribute("id");
298 |
299 | var state = this.stateOf(node);
300 | var feat = new OpenLayers.Feature.Vector(this.pointGeomFromNode(node), state);
301 | feat.tags = this.getTags(node);
302 | feat.osm_id = parseInt(id);
303 | feat.osm_version = parseInt(node.getAttribute("version"));
304 | feat.type = "node";
305 | feat.fid = "node." + feat.osm_id;
306 | feat.geometry.osm_id = feat.osm_id;
307 | feat.action = this.getAction(node);
308 | feat.used = !!usedNodes[feat.osm_id];
309 |
310 | if (this.isInExtent(node))
311 | {
312 | nodes[id + "." + state.state] = feat;
313 | this.pushTextualResult(feat);
314 | }
315 | }
316 | return nodes;
317 | },
318 |
319 | getWays: function(doc) {
320 | var way_list = doc.getElementsByTagName("way");
321 | var ways = {};
322 | for (var i = 0; i < way_list.length; ++i)
323 | {
324 | var way = way_list[i];
325 | var id = way.getAttribute("id");
326 |
327 | var state = this.stateOf(way);
328 | var way_nodes = this.pointListFromWay(way.getElementsByTagName("nd"));
329 | var feat = new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(way_nodes), state);
330 | feat.tags = this.getTags(way);
331 | feat.osm_id = parseInt(id);
332 | feat.osm_version = parseInt(way.getAttribute("version"));
333 | feat.type = "way";
334 | feat.fid = "way." + feat.osm_id;
335 | feat.action = this.getAction(way);
336 |
337 | // do add (deleted) ways without geometry for popup info
338 | // if (way_nodes.length >= 2)
339 | // {
340 | ways[id + "." + state.state] = feat;
341 | this.pushTextualResult(feat);
342 | // }
343 | }
344 | return ways;
345 | },
346 |
347 | getRelations: function(doc) {
348 | var relation_list = doc.getElementsByTagName("relation");
349 | var return_relations = {};
350 | for (var i = 0; i < relation_list.length; ++i)
351 | {
352 | var relation = relation_list[i];
353 | var id = relation.getAttribute("id");
354 |
355 | var member_list = relation.getElementsByTagName("member");
356 | var geom = new OpenLayers.Geometry.Collection();
357 | var component;
358 | for (var j = 0; j < member_list.length; ++j)
359 | {
360 | component = null;
361 | if (member_list[j].getAttribute("type") == "node" && this.isInExtent(member_list[j]))
362 | component = this.pointGeomFromNode(member_list[j]);
363 | else if (member_list[j].getAttribute("type") == "way")
364 | {
365 | var way_nodes = this.pointListFromWay(member_list[j].getElementsByTagName("nd"));
366 | if (way_nodes.length >= 2)
367 | component = new OpenLayers.Geometry.LineString(way_nodes);
368 | }
369 | if (component) {
370 | component.ref = member_list[j].getAttribute("ref");
371 | component.role = member_list[j].getAttribute("role");
372 | geom.addComponent(component);
373 | }
374 | }
375 |
376 | var state = this.stateOf(relation);
377 | var feat = new OpenLayers.Feature.Vector(geom, state);
378 | feat.tags = this.getTags(relation);
379 | feat.osm_id = parseInt(id);
380 | feat.osm_version = parseInt(relation.getAttribute("version"));
381 | feat.type = "relation";
382 | feat.fid = "relation." + feat.osm_id;
383 | feat.action = this.getAction(relation);
384 |
385 | // do add (deleted) relations without geometry for popup info
386 | // if (geom.components.length > 0)
387 | // {
388 | return_relations[id + "." + state.state] = feat;
389 | this.pushTextualResult(feat);
390 | // }
391 | }
392 | return return_relations;
393 | },
394 |
395 | strategy: null,
396 |
397 | CLASS_NAME: "OSMDiffFormat"
398 | });
399 |
400 | //-----------------------------------------------------------------------------
401 |
402 | function make_large_layer(data_url, color, zoom) {
403 |
404 | var styleMap = new OpenLayers.StyleMap({
405 | strokeColor: color,
406 | strokeOpacity: 0.5,
407 | strokeWidth: 6,
408 | pointRadius: 10,
409 | fillColor: color,
410 | fillOpacity: 0.25
411 | });
412 |
413 | var strategy = new ZoomLimitedBBOXStrategy(zoom);
414 | var layer = new OpenLayers.Layer.Vector(color, {
415 | strategies: [strategy],
416 | protocol: new OpenLayers.Protocol.HTTP({
417 | url: data_url,
418 | format: new OSMTimeoutFormat(strategy)
419 | }),
420 | styleMap: styleMap,
421 | projection: new OpenLayers.Projection("EPSG:4326"),
422 | ratio: 1.0
423 | });
424 |
425 | layer.events.register("featuresadded", layer, make_features_added_closure(layer));
426 |
427 | return layer;
428 | }
429 |
430 | function make_layer(data_url, color) {
431 | return make_large_layer(data_url, color, 8);
432 | }
433 |
--------------------------------------------------------------------------------
/licenses/momentjs-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011-2012 Tim Wood
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/licenses/openlayers-authors.txt:
--------------------------------------------------------------------------------
1 | OpenLayers contributors:
2 |
3 | Antoine Abt
4 | Mike Adair
5 | Jeff Adams
6 | Seb Benthall
7 | Bruno Binet
8 | Stéphane Brunner
9 | Howard Butler
10 | Bertil Chaupis
11 | John Cole
12 | Tim Coulter
13 | Robert Coup
14 | Jeff Dege
15 | Roald de Wit
16 | Schuyler Erle
17 | Christian López Espínola
18 | John Frank
19 | Sean Gilles
20 | Pierre Giraud
21 | Ivan Grcic
22 | Andreas Hocevar
23 | Marc Jansen
24 | Ian Johnson
25 | Frédéric Junod
26 | Eric Lemoine
27 | Philip Lindsay
28 | Martijn van Oosterhout
29 | David Overstrom
30 | Corey Puffault
31 | Peter William Robins
32 | Gregers Rygg
33 | Tim Schaub
34 | Christopher Schmidt
35 | Cameron Shorter
36 | Pedro Simonetti
37 | Paul Spencer
38 | Paul Smith
39 | Glen Stampoultzis
40 | James Stembridge
41 | Erik Uzureau
42 | Bart van den Eijnden
43 | Ivan Willig
44 | Thomas Wood
45 | Bill Woodall
46 | Steve Woodbridge
47 | David Zwarg
48 |
49 | Some portions of OpenLayers are used under the Apache 2.0 license, available
50 | in doc/licenses/APACHE-2.0.txt.
51 |
52 | Some portions of OpenLayers are used under the MIT license, availabie in
53 | doc/licenses/MIT-LICENSE.txt.
54 |
55 | Some portions of OpenLayers are Copyright 2001 Robert Penner, and are used
56 | under the BSD license, available in doc/licenses/BSD-LICENSE.txt
57 |
--------------------------------------------------------------------------------
/licenses/openlayers-license.txt:
--------------------------------------------------------------------------------
1 | Copyright 2005-2012 OpenLayers Contributors. All rights reserved. See
2 | authors.txt for full list.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation and/or
12 | other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY OPENLAYERS CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
15 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
16 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
17 | SHALL COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
18 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
21 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
22 | OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
23 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | The views and conclusions contained in the software and documentation are those
26 | of the authors and should not be interpreted as representing official policies,
27 | either expressed or implied, of OpenLayers Contributors.
28 |
--------------------------------------------------------------------------------
/licenses/openlayers_themes-LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010, Development Seed, Inc.
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | - Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 | - Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | - Neither the name of the Development Seed, Inc. nor the names of its
13 | contributors may be used to endorse or promote products derived from this
14 | software without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
--------------------------------------------------------------------------------
/licenses/underscorejs-LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009-2012 Jeremy Ashkenas, DocumentCloud
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # achavi - Augmented OSM Change Viewer
2 |
3 | Displays [OpenStreetMap](openstreetmap.org) changes based on the [Overpass API](https://overpass-api.de/) [Augmented Delta (adiff)](https://wiki.openstreetmap.org/wiki/Overpass_API/Overpass_QL#Augmented_Delta_between_two_dates_.28.22adiff.22.29) query and the
4 | [Augmented Diff](https://wiki.openstreetmap.org/wiki/Overpass_API/Augmented_Diffs#Contained_data) format.
5 |
6 | https://overpass-api.de/achavi/ - production (master)
7 | https://nrenner.github.io/achavi/ - development, test (gh-pages)
8 |
9 | *work in progress*
10 |
11 | See [olex](https://github.com/nrenner/olex) repository for OpenLayers-related sources (included as lib/olex.js).
12 |
13 | ## License
14 |
15 | Copyright (c) 2018 Norbert Renner and [contributors](https://github.com/nrenner/achavi/graphs/contributors), licensed under the [MIT License (MIT)](LICENSE)
16 |
17 | ## Install
18 |
19 | Achavi is a pure JavaScript Browser app. It relies on the Overpass API for serving Augmented Diffs.
20 |
21 | The project layout is already the runnable web app, there currently is no build. All files and directories are required at runtime,
22 | except readme.md.
23 |
24 | ## Licenses
25 |
26 | * [OpenLayers](http://www.openlayers.org/): Copyright (c) 2005-2012 OpenLayers [Contributors](licenses/openlayers-authors.txt), [2-clause BSD License](licenses/openlayers-license.txt)
27 | * [Underscore.js](http://underscorejs.org/): Copyright (c) 2009-2012 Jeremy Ashkenas, DocumentCloud, [MIT License](licenses/underscorejs-LICENSE)
28 | * [Moment.js](http://momentjs.com/): Copyright (c) 2011-2012 Tim Wood, [MIT License](licenses/momentjs-LICENSE)
29 | * [Yahoo! Secure XSS Filters](https://github.com/yahoo/xss-filters): Copyright (c) 2015, Yahoo! Inc., [Yahoo BSD license](https://github.com/yahoo/xss-filters/blob/master/LICENSE)
30 |
31 | * loadinggif-4.gif by http://loadinggif.com/
32 | * [layer-switcher-minimize.png](https://github.com/nrenner/openlayers_themes): Copyright (c) 2010, Development Seed, Inc., [3-clause BSD License](licenses/openlayers_themes-LICENSE.txt)
33 |
--------------------------------------------------------------------------------