diff --git a/build/license.txt b/build/license.txt index dfc0d1dfa7..b74e2a79e7 100644 --- a/build/license.txt +++ b/build/license.txt @@ -52,3 +52,41 @@ * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 */ + +/** + * Contains portions of Gears + * + * Copyright 2007, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Google Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Sets up google.gears.*, which is *the only* supported way to access Gears. + * + * Circumvent this file at your own risk! + * + * In the future, Gears may automatically define google.gears.* without this + * file. Gears may use these objects to transparently fix bugs and compatibility + * issues. Applications that use the code below will continue to work seamlessly + * when that happens. + */ diff --git a/examples/animator.js b/examples/animator.js new file mode 100644 index 0000000000..abe540377b --- /dev/null +++ b/examples/animator.js @@ -0,0 +1,670 @@ +/* + Animator.js 1.1.9 + + This library is released under the BSD license: + + Copyright (c) 2006, Bernard Sumption. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. Redistributions in binary + form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials + provided with the distribution. Neither the name BernieCode nor + the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + +*/ + + +// Applies a sequence of numbers between 0 and 1 to a number of subjects +// construct - see setOptions for parameters +function Animator(options) { + this.setOptions(options); + var _this = this; + this.timerDelegate = function(){_this.onTimerEvent()}; + this.subjects = []; + this.target = 0; + this.state = 0; + this.lastTime = null; +}; +Animator.prototype = { + // apply defaults + setOptions: function(options) { + this.options = Animator.applyDefaults({ + interval: 20, // time between animation frames + duration: 400, // length of animation + onComplete: function(){}, + onStep: function(){}, + transition: Animator.tx.easeInOut + }, options); + }, + // animate from the current state to provided value + seekTo: function(to) { + this.seekFromTo(this.state, to); + }, + // animate from the current state to provided value + seekFromTo: function(from, to) { + this.target = Math.max(0, Math.min(1, to)); + this.state = Math.max(0, Math.min(1, from)); + this.lastTime = new Date().getTime(); + if (!this.intervalId) { + this.intervalId = window.setInterval(this.timerDelegate, this.options.interval); + } + }, + // animate from the current state to provided value + jumpTo: function(to) { + this.target = this.state = Math.max(0, Math.min(1, to)); + this.propagate(); + }, + // seek to the opposite of the current target + toggle: function() { + this.seekTo(1 - this.target); + }, + // add a function or an object with a method setState(state) that will be called with a number + // between 0 and 1 on each frame of the animation + addSubject: function(subject) { + this.subjects[this.subjects.length] = subject; + return this; + }, + // remove all subjects + clearSubjects: function() { + this.subjects = []; + }, + // forward the current state to the animation subjects + propagate: function() { + var value = this.options.transition(this.state); + for (var i=0; i= Math.abs(this.state - this.target)) { + this.state = this.target; + } else { + this.state += movement; + } + + try { + this.propagate(); + } finally { + this.options.onStep.call(this); + if (this.target == this.state) { + window.clearInterval(this.intervalId); + this.intervalId = null; + this.options.onComplete.call(this); + } + } + }, + // shortcuts + play: function() {this.seekFromTo(0, 1)}, + reverse: function() {this.seekFromTo(1, 0)}, + // return a string describing this Animator, for debugging + inspect: function() { + var str = "# 20) return; + } + }, + getStyle: function(state) { + state = this.from + ((this.to - this.from) * state); + if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")"; + if (this.property == 'opacity') return state; + return Math.round(state) + this.units; + }, + inspect: function() { + return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n"; + } +} + +// animates a colour based style property between two hex values +function ColorStyleSubject(els, property, from, to) { + this.els = Animator.makeArray(els); + this.property = Animator.camelize(property); + this.to = this.expandColor(to); + this.from = this.expandColor(from); + this.origFrom = from; + this.origTo = to; +} + +ColorStyleSubject.prototype = { + // parse "#FFFF00" to [256, 256, 0] + expandColor: function(color) { + var hexColor, red, green, blue; + hexColor = ColorStyleSubject.parseColor(color); + if (hexColor) { + red = parseInt(hexColor.slice(1, 3), 16); + green = parseInt(hexColor.slice(3, 5), 16); + blue = parseInt(hexColor.slice(5, 7), 16); + return [red,green,blue] + } + if (window.DEBUG) { + alert("Invalid colour: '" + color + "'"); + } + }, + getValueForState: function(color, state) { + return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state)); + }, + setState: function(state) { + var color = '#' + + ColorStyleSubject.toColorPart(this.getValueForState(0, state)) + + ColorStyleSubject.toColorPart(this.getValueForState(1, state)) + + ColorStyleSubject.toColorPart(this.getValueForState(2, state)); + for (var i=0; i 255) number = 255; + var digits = number.toString(16); + if (number < 16) return '0' + digits; + return digits; +} +ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i; +ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; + +// Animates discrete styles, i.e. ones that do not scale but have discrete values +// that can't be interpolated +function DiscreteStyleSubject(els, property, from, to, threshold) { + this.els = Animator.makeArray(els); + this.property = Animator.camelize(property); + this.from = from; + this.to = to; + this.threshold = threshold || 0.5; +} + +DiscreteStyleSubject.prototype = { + setState: function(state) { + var j=0; + for (var i=0; i section ? 1 : 0); + } + if (this.options.rememberance) { + document.location.hash = this.rememberanceTexts[section]; + } + } +} diff --git a/examples/measure.html b/examples/measure.html index 94bc4000e1..71aaee20b9 100644 --- a/examples/measure.html +++ b/examples/measure.html @@ -92,6 +92,31 @@ document.getElementById('noneToggle').checked = true; } + + function calcVincenty(geometry) { + /** + * Note: this function assumes geographic coordinates and + * will fail otherwise. OpenLayers.Util.distVincenty takes + * two objects representing points with geographic coordinates + * and returns the geodesic distance between them (shortest + * distance between the two points on an ellipsoid) in *kilometers*. + * + * It is important to realize that the segments drawn on the map + * are *not* geodesics (or "great circle" segments). This means + * that in general, the measure returned by this function + * will not represent the length of segments drawn on the map. + */ + var dist = 0; + for (var i = 1; i < geometry.components.length; i++) { + var first = geometry.components[i-1]; + var second = geometry.components[i]; + dist += OpenLayers.Util.distVincenty( + {lon: first.x, lat: first.y}, + {lon: second.x, lat: second.y} + ); + } + return dist; + } function handleMeasurements(event) { var geometry = event.geometry; @@ -102,6 +127,10 @@ var out = ""; if(order == 1) { out += "measure: " + measure.toFixed(3) + " " + units; + if (map.getProjection() == "EPSG:4326") { + out += "
Great Circle Distance: " + + calcVincenty(geometry).toFixed(3) + " km *"; + } } else { out += "measure: " + measure.toFixed(3) + " " + units + "2"; } @@ -144,6 +173,12 @@ +

* Note that the geometries drawn are planar geometries and the + metrics returned by the measure control are planar measures. The + "great circle" distance does not necessarily represent the length + of the segments drawn on the map. Instead, it is a geodesic metric that + represents the cumulative shortest path between all vertices in the + geometry were they projected onto a sphere.

diff --git a/examples/protocol-gears.html b/examples/protocol-gears.html new file mode 100644 index 0000000000..6bbfb5f030 --- /dev/null +++ b/examples/protocol-gears.html @@ -0,0 +1,261 @@ + + + + + + + + + +

Gears Protocol Example

+ +
+
+

+ Shows the usage of the Gears protocol. +

+ +
+
+
+ +
+ Sync +

The Sync link destroys the features currently in the layer, reads + features from the Gears database, and adds them to the layer. + Uncommitted features will be lost.

+ + Commit +

The Commit link commits to the Gears database the features that are + marked as INSERT, UPDATE or DELETE.

+ + Delete +

The Delete link marks the selected feature as DELETE. To select a feature + click choose the navigation control in the editing toolbar.

+
+ +
+

Status:

+

Result:

+
+ +
+

This example demonstrates the usage of OpenLayers Gears protocol to + read/create/update/delete features from/to the Gears database. + Gears must obviously be installed + in your browser for this example to work.

+
+ + diff --git a/examples/proxy.cgi b/examples/proxy.cgi index a3f78be57a..af5c3b8038 100755 --- a/examples/proxy.cgi +++ b/examples/proxy.cgi @@ -18,7 +18,7 @@ import sys, os allowedHosts = ['www.openlayers.org', 'openlayers.org', 'labs.metacarta.com', 'world.freemap.in', 'prototype.openmnnd.org', 'geo.openplans.org', - 'sigma.openplans.org' + 'sigma.openplans.org', 'www.openstreetmap.org'] method = os.environ["REQUEST_METHOD"] @@ -40,8 +40,8 @@ try: print "Status: 502 Bad Gateway" print "Content-Type: text/plain" print - print "This proxy does not allow you to access that location." - print + print "This proxy does not allow you to access that location (%s)." % (host,) + print print os.environ elif url.startswith("http://") or url.startswith("https://"): diff --git a/examples/spherical-mercator.html b/examples/spherical-mercator.html index c4ded648a8..cc4da86752 100644 --- a/examples/spherical-mercator.html +++ b/examples/spherical-mercator.html @@ -28,6 +28,7 @@ projection: new OpenLayers.Projection("EPSG:900913"), displayProjection: new OpenLayers.Projection("EPSG:4326"), units: "m", + numZoomLevels: 18, maxResolution: 156543.0339, maxExtent: new OpenLayers.Bounds(-20037508, -20037508, 20037508, 20037508.34) @@ -86,8 +87,8 @@ attribution: 'OpenStreetMap' } ); - // create OSM layer - var mapnik = new OpenLayers.Layer.TMS( + // create OAM layer + var oam = new OpenLayers.Layer.TMS( "OpenAerialMap", "http://tile.openaerialmap.org/tiles/1.0.0/openaerialmap-900913/", { @@ -95,10 +96,20 @@ } ); + // create OSM layer + var mapnik = new OpenLayers.Layer.TMS( + "OpenStreetMap (Mapnik)", + "http://a.tile.openstreetmap.org/", + { + type: 'png', getURL: osm_getTileURL, + displayOutsideMaxExtent: true, + attribution: 'OpenStreetMap' + } + ); // create OSM layer var osmarender = new OpenLayers.Layer.TMS( "OpenStreetMap (Tiles@Home)", - "http://tah.openstreetmap.org/Tiles/tile.php/", + "http://tah.openstreetmap.org/Tiles/tile/", { type: 'png', getURL: osm_getTileURL, displayOutsideMaxExtent: true, @@ -122,7 +133,7 @@ var vector = new OpenLayers.Layer.Vector("Editable Vectors"); map.addLayers([gmap, gsat, ghyb, veroad, veaer, vehyb, - yahoo, yahoosat, yahoohyb, mapnik, osmarender, + yahoo, yahoosat, yahoohyb, oam, mapnik, osmarender, wms, vector]); map.addControl(new OpenLayers.Control.LayerSwitcher()); map.addControl(new OpenLayers.Control.EditingToolbar(vector)); diff --git a/examples/strategy-bbox.html b/examples/strategy-bbox.html new file mode 100644 index 0000000000..173d33f1f7 --- /dev/null +++ b/examples/strategy-bbox.html @@ -0,0 +1,64 @@ + + + OpenLayers BBOX Strategy Example + + + + + + +

BBOX Strategy Example

+

+ Uses a BBOX strategy to request features within a bounding box. +

+
+
+

The BBOX strategy requests data within a bounding box. When the + previously requested data bounds are invalidated (by browsing to + some area not covered by those bounds), another request for data + is issued.

+
+ + diff --git a/examples/strategy-cluster.html b/examples/strategy-cluster.html new file mode 100644 index 0000000000..91e1e428cd --- /dev/null +++ b/examples/strategy-cluster.html @@ -0,0 +1,201 @@ + + + OpenLayers Cluster Strategy Example + + + + + + + + + +

Cluster Strategy Example

+

+ Uses a cluster strategy to render points representing clusters of features. +

+
+
+

The Cluster strategy lets you display points representing clusters + of features within some pixel distance.

+
+
+

Hover over a cluster on the map to see the photos it includes.

+
+
+
<<
+
+ +
+
>>
+
+
+ + diff --git a/examples/strategy-paging.html b/examples/strategy-paging.html new file mode 100644 index 0000000000..3e0d54f62c --- /dev/null +++ b/examples/strategy-paging.html @@ -0,0 +1,78 @@ + + + OpenLayers Paging Strategy Example + + + + + + +

Paging Strategy Example

+

+ Uses a paging strategy to cache large batches of features and render a page at a time. +

+
+ Displaying page 0 of ... + + +

+
+

The Paging strategy lets you apply client side paging for protocols + that do not support paging on the server. In this case, the protocol requests a + batch of 100 features, the strategy caches those and supplies a single + page at a time to the layer.

+
+ + diff --git a/examples/vector-formats.html b/examples/vector-formats.html index e01406048b..714f12d939 100644 --- a/examples/vector-formats.html +++ b/examples/vector-formats.html @@ -52,24 +52,38 @@ var in_options = { 'internalProjection': map.baseLayer.projection, 'externalProjection': new OpenLayers.Projection(OpenLayers.Util.getElement("inproj").value) - } + }; var out_options = { 'internalProjection': map.baseLayer.projection, 'externalProjection': new OpenLayers.Projection(OpenLayers.Util.getElement("outproj").value) - } + }; + var gmlOptions = { + featureType: "feature", + featureNS: "http://example.com/feature" + }; + var gmlOptionsIn = OpenLayers.Util.extend( + OpenLayers.Util.extend({}, gmlOptions), + in_options + ); + var gmlOptionsOut = OpenLayers.Util.extend( + OpenLayers.Util.extend({}, gmlOptions), + out_options + ); formats = { 'in': { wkt: new OpenLayers.Format.WKT(in_options), geojson: new OpenLayers.Format.GeoJSON(in_options), georss: new OpenLayers.Format.GeoRSS(in_options), - gml: new OpenLayers.Format.GML(in_options), + gml2: new OpenLayers.Format.GML.v2(gmlOptionsIn), + gml3: new OpenLayers.Format.GML.v3(gmlOptionsIn), kml: new OpenLayers.Format.KML(in_options) }, 'out': { wkt: new OpenLayers.Format.WKT(out_options), geojson: new OpenLayers.Format.GeoJSON(out_options), georss: new OpenLayers.Format.GeoRSS(out_options), - gml: new OpenLayers.Format.GML(out_options), + gml2: new OpenLayers.Format.GML.v2(gmlOptionsOut), + gml3: new OpenLayers.Format.GML.v3(gmlOptionsOut), kml: new OpenLayers.Format.KML(out_options) } }; @@ -169,7 +183,8 @@ - + +   diff --git a/examples/wfs-reprojection.html b/examples/wfs-reprojection.html index 1076fa430a..ef45be0848 100644 --- a/examples/wfs-reprojection.html +++ b/examples/wfs-reprojection.html @@ -45,7 +45,7 @@ // (only if your wfs doens't support your map projection) var wfs = layer = new OpenLayers.Layer.WFS( "States (SVG)", - "http://sigma.openplans.org:8080/geoserver/ows", + "http://sigma.openplans.org/geoserver/ows", {typename: 'topp:states'}, { typename: 'states', @@ -60,7 +60,7 @@ var wfs = layer = new OpenLayers.Layer.WFS( "States (Canvas)", - "http://sigma.openplans.org:8080/geoserver/ows", + "http://sigma.openplans.org/geoserver/ows", {typename: 'topp:states'}, { typename: 'states', diff --git a/lib/Gears/gears_init.js b/lib/Gears/gears_init.js new file mode 100644 index 0000000000..d531a3f5bb --- /dev/null +++ b/lib/Gears/gears_init.js @@ -0,0 +1,88 @@ +/* + * Copyright 2007, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Google Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Sets up google.gears.*, which is *the only* supported way to access Gears. + * + * Circumvent this file at your own risk! + * + * In the future, Gears may automatically define google.gears.* without this + * file. Gears may use these objects to transparently fix bugs and compatibility + * issues. Applications that use the code below will continue to work seamlessly + * when that happens. + */ + +(function() { + // We are already defined. Hooray! + if (window.google && google.gears) { + return; + } + + var factory = null; + + // Firefox + if (typeof GearsFactory != 'undefined') { + factory = new GearsFactory(); + } else { + // IE + try { + factory = new ActiveXObject('Gears.Factory'); + // privateSetGlobalObject is only required and supported on WinCE. + if (factory.getBuildInfo().indexOf('ie_mobile') != -1) { + factory.privateSetGlobalObject(this); + } + } catch (e) { + // Safari + if ((typeof navigator.mimeTypes != 'undefined') + && navigator.mimeTypes["application/x-googlegears"]) { + factory = document.createElement("object"); + factory.style.display = "none"; + factory.width = 0; + factory.height = 0; + factory.type = "application/x-googlegears"; + document.documentElement.appendChild(factory); + } + } + } + + // *Do not* define any objects if Gears is not installed. This mimics the + // behavior of Gears defining the objects in the future. + if (!factory) { + return; + } + + // Now set up the objects, being careful not to overwrite anything. + // + // Note: In Internet Explorer for Windows Mobile, you can't add properties to + // the window object. However, global objects are automatically added as + // properties of the window object in all browsers. + if (!window.google) { + google = {}; + } + + if (!google.gears) { + google.gears = {factory: factory}; + } +})(); diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 43cdff7f78..4beda022ad 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -83,6 +83,7 @@ "OpenLayers/Tween.js", "Rico/Corner.js", "Rico/Color.js", + "Gears/gears_init.js", "OpenLayers/Ajax.js", "OpenLayers/Request.js", "OpenLayers/Request/XMLHttpRequest.js", @@ -164,6 +165,7 @@ "OpenLayers/Control/Panel.js", "OpenLayers/Control/SelectFeature.js", "OpenLayers/Control/NavigationHistory.js", + "OpenLayers/Control/Measure.js", "OpenLayers/Geometry.js", "OpenLayers/Geometry/Rectangle.js", "OpenLayers/Geometry/Collection.js", @@ -184,8 +186,13 @@ "OpenLayers/Layer/Vector.js", "OpenLayers/Strategy.js", "OpenLayers/Strategy/Fixed.js", + "OpenLayers/Strategy/Cluster.js", + "OpenLayers/Strategy/Paging.js", + "OpenLayers/Strategy/BBOX.js", "OpenLayers/Protocol.js", "OpenLayers/Protocol/HTTP.js", + "OpenLayers/Protocol/SQL.js", + "OpenLayers/Protocol/SQL/Gears.js", "OpenLayers/Layer/PointTrack.js", "OpenLayers/Layer/GML.js", "OpenLayers/Style.js", @@ -199,6 +206,9 @@ "OpenLayers/Format.js", "OpenLayers/Format/XML.js", "OpenLayers/Format/GML.js", + "OpenLayers/Format/GML/Base.js", + "OpenLayers/Format/GML/v2.js", + "OpenLayers/Format/GML/v3.js", "OpenLayers/Format/KML.js", "OpenLayers/Format/GeoRSS.js", "OpenLayers/Format/WFS.js", diff --git a/lib/OpenLayers/Control/Permalink.js b/lib/OpenLayers/Control/Permalink.js index 6eb0849ddb..34013a7023 100644 --- a/lib/OpenLayers/Control/Permalink.js +++ b/lib/OpenLayers/Control/Permalink.js @@ -168,8 +168,6 @@ OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, { */ createParams: function(center, zoom, layers) { center = center || this.map.getCenter(); - zoom = zoom || this.map.getZoom(); - layers = layers || this.map.layers; var params = OpenLayers.Util.getParameters(this.base); @@ -177,8 +175,11 @@ OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, { // Break out of this function, and simply return the params from the // base link. if (center) { - - params.zoom = this.map.getZoom(); + + //zoom + params.zoom = zoom || this.map.getZoom(); + + //lon,lat var lat = center.lat; var lon = center.lon; @@ -192,10 +193,12 @@ OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, { } params.lat = Math.round(lat*100000)/100000; params.lon = Math.round(lon*100000)/100000; - + + //layers + layers = layers || this.map.layers; params.layers = ''; - for (var i=0, len=this.map.layers.length; i} When passed a externalProjection and @@ -54,6 +60,14 @@ OpenLayers.Format = OpenLayers.Class({ */ initialize: function(options) { OpenLayers.Util.extend(this, options); + this.options = options; + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { }, /** diff --git a/lib/OpenLayers/Format/GML/Base.js b/lib/OpenLayers/Format/GML/Base.js new file mode 100644 index 0000000000..7562db737c --- /dev/null +++ b/lib/OpenLayers/Format/GML/Base.js @@ -0,0 +1,525 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + */ + +/** + * Eventually, this will require the OpenLayers.Format.GML. For now, since + * this parser can be included in a lib without the old GML parser, we + * declare the namespace if it doesn't exist. + */ +if(!OpenLayers.Format.GML) { + OpenLayers.Format.GML = {}; +} + +/** + * Class: OpenLayers.Format.GML.Base + * Superclass for GML parsers. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + gml: "http://www.opengis.net/gml", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "gml", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * APIProperty: featureType + * {String} The local (without prefix) feature typeName. + */ + featureType: null, + + /** + * APIProperty: featureNS + * {String} The feature namespace. Must be set in the options at + * construction. + */ + featureNS: null, + + /** + * APIProperty: geometry + * {String} Name of geometry element. Defaults to "geometry". + */ + geometryName: "geometry", + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. Default is true. + */ + extractAttributes: true, + + /** + * APIProperty: srsName + * {String} URI for spatial reference system. This is optional for + * single part geometries and mandatory for collections and multis. + * If set, the srsName attribute will be written for all geometries. + * Default is null. + */ + srsName: null, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Constructor: OpenLayers.Format.GML.Base + * Instances of this class are not created directly. Use the + * or constructor + * instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + this.setNamespace("feature", options.featureNS); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} A gml:featureMember element, a gml:featureMembers + * element, or an element containing either of the above at any level. + * + * Returns: + * {Array()} An array of features. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var features = []; + this.readNode(data, {features: features}); + if(features.length == 0) { + // look for gml:featureMember elements + var elements = this.getElementsByTagNameNS( + data, this.namespaces.gml, "featureMember" + ); + if(elements.length) { + for(var i=0, len=elements.length; i) | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": { + "featureMember": function(feature) { + var node = this.createElementNSPlus("gml:featureMember"); + this.writeNode("feature:_typeName", feature, node); + return node; + }, + "MultiPoint": function(geometry) { + var node = this.createElementNSPlus("gml:MultiPoint"); + for(var i=0; i + */ +OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd", + + /** + * Constructor: OpenLayers.Format.GML.v2 + * Create a parser for GML v2. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "gml": OpenLayers.Util.applyDefaults({ + "outerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.outer = obj.components[0]; + }, + "innerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.inner.push(obj.components[0]); + }, + "Box": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + // GML2 only has abstract feature collections + // wfs provides a feature collection from a well-known schema + name = "wfs:FeatureCollection"; + } else { + name = "gml:featureMember"; + } + var root = this.writeNode(name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": OpenLayers.Util.applyDefaults({ + "Point": function(geometry) { + var node = this.createElementNSPlus("gml:Point"); + this.writeNode("coordinates", [geometry], node); + return node; + }, + "coordinates": function(points) { + var numPoints = points.length; + var parts = new Array(numPoints); + var point; + for(var i=0; i + */ +OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. The writers + * conform with the Simple Features Profile for GML. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd", + + /** + * Constructor: OpenLayers.Format.GML.v3 + * Create a parser for GML v3. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "gml": OpenLayers.Util.applyDefaults({ + "featureMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "pos": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var point; + if(this.xy) { + point = new OpenLayers.Geometry.Point( + coords[0], coords[1], coords[2] + ); + } else { + point = new OpenLayers.Geometry.Point( + coords[1], coords[0], coords[2] + ); + } + obj.points = [point]; + }, + "posList": function(node, obj) { + var str = this.concatChildValues(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var dim = parseInt(node.getAttribute("dimension")) || 2; + var j, x, y, z; + var numPoints = coords.length / dim; + var points = new Array(numPoints); + for(var i=0, len=coords.length; i 0) { + container.components = [ + new OpenLayers.Geometry.MultiPolygon(obj.components) + ]; + } + }, + "surfaceMember": function(node, obj) { + this.readChildNodes(node, obj); + }, + "surfaceMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "pointMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "lineStringMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "polygonMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "geometryMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Envelope": function(node, container) { + var obj = {points: new Array(2)}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + }, + "lowerCorner": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj) + container.points[0] = obj.points[0]; + }, + "upperCorner": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj) + container.points[1] = obj.points[0]; + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": OpenLayers.Util.applyDefaults({ + "featureMembers": function(features) { + var node = this.createElementNSPlus("gml:featureMembers"); + for(var i=0, len=features.length; i to add or set a namespace alias after construction. + */ + namespaces: null, + + /** + * Property: namespaceAlias + * {Object} Mapping of namespace URI to namespace alias. This object + * is read-only. Use to add or set a namespace alias. + */ + namespaceAlias: null, + + /** + * Property: defaultPrefix + * {String} The default namespace alias for creating element nodes. + */ + defaultPrefix: null, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: {}, + + /** + * Property: writers + * As a compliment to the property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: {}, + /** * Property: xmldom * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM @@ -44,6 +84,34 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { this.xmldom = new ActiveXObject("Microsoft.XMLDOM"); } OpenLayers.Format.prototype.initialize.apply(this, [options]); + // clone the namespace object and set all namespace aliases + this.namespaces = OpenLayers.Util.extend({}, this.namespaces); + this.namespaceAlias = {}; + for(var alias in this.namespaces) { + this.namespaceAlias[this.namespaces[alias]] = alias; + } + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + this.xmldom = null; + OpenLayers.Format.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setNamespace + * Set a namespace alias and URI for the format. + * + * Parameters: + * alias - {String} The namespace alias (prefix). + * uri - {String} The namespace URI. + */ + setNamespace: function(alias, uri) { + this.namespaces[alias] = uri; + this.namespaceAlias[uri] = alias; }, /** @@ -289,11 +357,12 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { * {String} The value of the first child of the given node. */ getChildValue: function(node, def) { - var value; - if (node && node.firstChild && node.firstChild.nodeValue) { - value = node.firstChild.nodeValue; - } else { - value = (def != undefined) ? def : ""; + var value = def || ""; + if(node) { + var child = node.firstChild; + if(child) { + value = child.nodeValue || value; + } } return value; }, @@ -384,6 +453,172 @@ OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, { } }, + /** + * Method: createElementNSPlus + * Shorthand for creating namespaced elements with optional attributes and + * child text nodes. + * + * Parameters: + * name - {String} The qualified node name. + * options - {Object} Optional object for node configuration. + * + * Valid options: + * uri - {String} Optional namespace uri for the element - supply a prefix + * instead if the namespace uri is a property of the format's namespace + * object. + * attributes - {Object} Optional attributes to be set using the + * method. + * value - {String} Optional text to be appended as a text node. + * + * Returns: + * {Element} An element node. + */ + createElementNSPlus: function(name, options) { + options = options || {}; + var loc = name.indexOf(":"); + // order of prefix preference + // 1. in the uri option + // 2. in the prefix option + // 3. in the qualified name + // 4. from the defaultPrefix + var uri = options.uri || this.namespaces[options.prefix]; + if(!uri) { + loc = name.indexOf(":"); + uri = this.namespaces[name.substring(0, loc)]; + } + if(!uri) { + uri = this.namespaces[this.defaultPrefix]; + } + var node = this.createElementNS(uri, name); + if(options.attributes) { + this.setAttributes(node, options.attributes); + } + if(options.value) { + node.appendChild(this.createTextNode(options.value)); + } + return node; + }, + + /** + * Method: setAttributes + * Set multiple attributes given key value pairs from an object. + * + * Parameters: + * node - {Element} An element node. + * obj - {Object || Array} An object whose properties represent attribute + * names and values represent attribute values. If an attribute name + * is a qualified name ("prefix:local"), the prefix will be looked up + * in the parsers {namespaces} object. If the prefix is found, + * setAttributeNS will be used instead of setAttribute. + */ + setAttributes: function(node, obj) { + var value, loc, alias, uri; + for(var name in obj) { + if(obj[name] != null && obj[name].toString) { + value = obj[name].toString(); + // check for qualified attribute name ("prefix:local") + uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null; + this.setAttributeNS(node, uri, name, value); + } + } + }, + + /** + * Method: readNode + * Shorthand for applying one of the named readers given the node + * namespace and local name. Readers take two args (node, obj) and + * generally extend or modify the second. + * + * Parameters: + * node - {DOMElement} The node to be read (required). + * obj - {Object} The object to be modified (optional). + * + * Returns: + * {Object} The input object, modified (or a new one if none was provided). + */ + readNode: function(node, obj) { + if(!obj) { + obj = {}; + } + var group = this.readers[this.namespaceAlias[node.namespaceURI]]; + if(group) { + var local = node.localName || node.nodeName.split(":").pop(); + var reader = group[local] || group["*"]; + if(reader) { + reader.apply(this, [node, obj]); + } + } + return obj; + }, + + /** + * Method: readChildNodes + * Shorthand for applying the named readers to all children of a node. + * For each child of type 1 (element), is called. + * + * Parameters: + * node - {DOMElement} The node to be read (required). + * obj - {Object} The object to be modified (optional). + * + * Returns: + * {Object} The input object, modified. + */ + readChildNodes: function(node, obj) { + if(!obj) { + obj = {}; + } + var children = node.childNodes; + var child; + for(var i=0, len=children.length; i group. If a local name is used (e.g. "Name") then + * the namespace of the parent is assumed. If a local name is used + * and no parent is supplied, then the default namespace is assumed. + * obj - {Object} Structure containing data for the writer. + * parent - {DOMElement} Result will be appended to this node. If no parent + * is supplied, the node will not be appended to anything. + * + * Returns: + * {DOMElement} The child node. + */ + writeNode: function(name, obj, parent) { + var prefix, local; + var split = name.indexOf(":"); + if(split > 0) { + prefix = name.substring(0, split); + local = name.substring(split + 1); + } else { + if(parent) { + prefix = this.namespaceAlias[parent.namespaceURI]; + } else { + prefix = this.defaultPrefix; + } + local = name; + } + var child = this.writers[prefix][local].apply(this, [obj]); + if(parent) { + parent.appendChild(child); + } + return child; + }, + CLASS_NAME: "OpenLayers.Format.XML" }); diff --git a/lib/OpenLayers/Layer/TMS.js b/lib/OpenLayers/Layer/TMS.js index 15d88e246a..8892bb82e8 100644 --- a/lib/OpenLayers/Layer/TMS.js +++ b/lib/OpenLayers/Layer/TMS.js @@ -5,6 +5,7 @@ /** * @requires OpenLayers/Layer/Grid.js + * @requires OpenLayers/Tile/Image.js */ /** diff --git a/lib/OpenLayers/Layer/Vector.js b/lib/OpenLayers/Layer/Vector.js index 870f6cb566..d27c3f06df 100644 --- a/lib/OpenLayers/Layer/Vector.js +++ b/lib/OpenLayers/Layer/Vector.js @@ -38,7 +38,12 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { * Supported map event types (in addition to those from ): * - *beforefeatureadded* Triggered before a feature is added. Listeners * will receive an object with a *feature* property referencing the - * feature to be added. + * feature to be added. To stop the feature from being added, a + * listener should return false. + * - *beforefeaturesadded* Triggered before an array of features is added. + * Listeners will receive an object with a *features* property + * referencing the feature to be added. To stop the features from + * being added, a listener should return false. * - *featureadded* Triggered after a feature is added. The event * object passed to listeners will have a *feature* property with a * reference to the added feature. @@ -72,7 +77,8 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { * - *refresh* Triggered when something wants a strategy to ask the protocol * for a new set of features. */ - EVENT_TYPES: ["beforefeatureadded", "featureadded", "featuresadded", + EVENT_TYPES: ["beforefeatureadded", "beforefeaturesadded", + "featureadded", "featuresadded", "beforefeatureremoved", "featureremoved", "featuresremoved", "beforefeatureselected", "featureselected", "featureunselected", "beforefeaturemodified", "featuremodified", "afterfeaturemodified", @@ -407,19 +413,14 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { } if(!zoomChanged && coordSysUnchanged) { - var unrenderedFeatures = {}; for(var i in this.unrenderedFeatures) { var feature = this.unrenderedFeatures[i]; - if(!this.drawFeature(feature)) { - unrenderedFeatures[i] = feature; - } + this.drawFeature(feature); } - this.unrenderedFeatures = unrenderedFeatures; } } if (!this.drawn || zoomChanged || !coordSysUnchanged) { - this.unrenderedFeatures = {}; this.drawn = true; var feature; for(var i=0, len=this.features.length; i} * style - {Object} Symbolizer hash or {String} renderIntent - * - * Returns: - * {Boolean} true if the renderer was able to draw the feature, false - * otherwise */ drawFeature: function(feature, style) { if (typeof style != "object") { @@ -618,7 +621,11 @@ OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, { } } - return this.renderer.drawFeature(feature, style); + if (!this.renderer.drawFeature(feature, style)) { + this.unrenderedFeatures[feature.id] = feature; + } else { + delete this.unrenderedFeatures[feature.id]; + }; }, /** diff --git a/lib/OpenLayers/Map.js b/lib/OpenLayers/Map.js index b3358db739..c640895dd6 100644 --- a/lib/OpenLayers/Map.js +++ b/lib/OpenLayers/Map.js @@ -167,7 +167,14 @@ OpenLayers.Map = OpenLayers.Class({ /** * Property: controls - * {Array()} List of controls associated with the map + * {Array()} List of controls associated with the map. + * + * If not provided in the map options at construction, the map will + * be given the following controls by default: + * - + * - + * - + * - */ controls: null, diff --git a/lib/OpenLayers/Protocol/HTTP.js b/lib/OpenLayers/Protocol/HTTP.js index 866c8e318e..76c5fbc83d 100644 --- a/lib/OpenLayers/Protocol/HTTP.js +++ b/lib/OpenLayers/Protocol/HTTP.js @@ -112,6 +112,15 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { * options - {Object} Optional object for configuring the request. * This object is modified and should not be reused. * + * Valid options: + * url - {String} Url for the request. + * params - {Object} Parameters to get serialized as a query string. + * headers - {Object} Headers to be set on the request. + * filter - {} If a bbox filter is sent, it will be + * serialized according to the OpenSearch Geo extension + * (bbox=minx,miny,maxx,maxy). Note that a BBOX filter as the child + * of a logical filter will not be serialized. + * * Returns: * {} A response object, whose "priv" property * references the HTTP request, this object is also passed to the @@ -121,6 +130,14 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { read: function(options) { options = OpenLayers.Util.applyDefaults(options, this.options); var resp = new OpenLayers.Protocol.Response({requestType: "read"}); + + if(options.filter && options.filter instanceof OpenLayers.Filter.Spatial) { + if(options.filter.type == OpenLayers.Filter.Spatial.BBOX) { + options.params = OpenLayers.Util.extend(options.params, { + bbox: options.filter.value.toArray() + }); + } + } resp.priv = OpenLayers.Request.GET({ url: options.url, diff --git a/lib/OpenLayers/Protocol/SQL.js b/lib/OpenLayers/Protocol/SQL.js new file mode 100644 index 0000000000..27072cb0b0 --- /dev/null +++ b/lib/OpenLayers/Protocol/SQL.js @@ -0,0 +1,87 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Protocol.js + */ + +/** + * Class: OpenLayers.Protocol.SQL + * Abstract SQL protocol class. Not to be instantiated directly. Use + * one of the SQL protocol subclasses instead. + * + * Inherits from: + * - + */ +OpenLayers.Protocol.SQL = OpenLayers.Class(OpenLayers.Protocol, { + + /** + * APIProperty: databaseName + * {String} + */ + databaseName: 'ol', + + /** + * APIProperty: tableName + * Name of the database table into which Features should be saved. + */ + tableName: "ol_vector_features", + + /** + * Property: postReadFiltering + * {Boolean} Whether the filter (if there's one) must be applied after + * the features have been read from the database; for example the + * BBOX strategy passes the read method a BBOX spatial filter, if + * postReadFiltering is true every feature read from the database + * will go through the BBOX spatial filter, which can be costly; + * defaults to true. + */ + postReadFiltering: true, + + /** + * Constructor: OpenLayers.Protocol.SQL + */ + initialize: function(options) { + OpenLayers.Protocol.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: destroy + * Clean up the protocol. + */ + destroy: function() { + OpenLayers.Protocol.prototype.destroy.apply(this); + }, + + /** + * APIMethod: supported + * This should be overridden by specific subclasses + * + * Returns: + * {Boolean} Whether or not the browser supports the SQL backend + */ + supported: function() { + return false; + }, + + /** + * Method: evaluateFilter + * If postReadFiltering is true evaluate the filter against the feature + * and return the result of the evaluation, otherwise return true. + * + * Parameters: + * {} The feature. + * {} The filter. + * + * Returns: + * {Boolean} true if postReadFiltering if false, the result of the + * filter evaluation otherwise. + */ + evaluateFilter: function(feature, filter) { + return filter && this.postReadFiltering ? + filter.evaluate(feature) : true; + }, + + CLASS_NAME: "OpenLayers.Protocol.SQL" +}); diff --git a/lib/OpenLayers/Protocol/SQL/Gears.js b/lib/OpenLayers/Protocol/SQL/Gears.js new file mode 100644 index 0000000000..0c76b3d4ba --- /dev/null +++ b/lib/OpenLayers/Protocol/SQL/Gears.js @@ -0,0 +1,559 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires Gears/gears_init.js + * @requires OpenLayers/Protocol/SQL.js + * @requires OpenLayers/Format/JSON.js + * @requires OpenLayers/Format/WKT.js + */ + +/** + * Class: OpenLayers.Protocol.SQL.Gears + * This Protocol stores feature in the browser via the Gears Database module + * . + * + * The main advantage is that all the read, create, update and delete operations + * can be done offline. + * + * Inherits from: + * - + */ +OpenLayers.Protocol.SQL.Gears = OpenLayers.Class(OpenLayers.Protocol.SQL, { + + /** + * Property: FID_PREFIX + * {String} + */ + FID_PREFIX: '__gears_fid__', + + /** + * Property: NULL_GEOMETRY + * {String} + */ + NULL_GEOMETRY: '__gears_null_geometry__', + + /** + * Property: NULL_FEATURE_STATE + * {String} + */ + NULL_FEATURE_STATE: '__gears_null_feature_state__', + + /** + * Property: jsonParser + * {} + */ + jsonParser: null, + + /** + * Property: wktParser + * {} + */ + wktParser: null, + + /** + * Property: fidRegExp + * {RegExp} Regular expression to know whether a feature was + * created in offline mode. + */ + fidRegExp: null, + + /** + * Property: saveFeatureState + * {Boolean} Whether to save the feature state () + * into the database, defaults to true. + */ + saveFeatureState: true, + + /** + * Property: typeOfFid + * {String} The type of the feature identifier, either "number" or + * "string", defaults to "string". + */ + typeOfFid: "string", + + /** + * Property: db + * {GearsDatabase} + */ + db: null, + + /** + * Constructor: OpenLayers.Protocol.SQL.Gears + */ + initialize: function(options) { + if (!this.supported()) { + return; + } + OpenLayers.Protocol.SQL.prototype.initialize.apply(this, [options]); + this.jsonParser = new OpenLayers.Format.JSON(); + this.wktParser = new OpenLayers.Format.WKT(); + + this.fidRegExp = new RegExp('^' + this.FID_PREFIX); + this.initializeDatabase(); + + + }, + + /** + * Method: initializeDatabase + */ + initializeDatabase: function() { + this.db = google.gears.factory.create('beta.database'); + this.db.open(this.databaseName); + this.db.execute( + "CREATE TABLE IF NOT EXISTS " + this.tableName + + " (fid TEXT UNIQUE, geometry TEXT, properties TEXT," + + " state TEXT)"); + }, + + /** + * APIMethod: destroy + * Clean up the protocol. + */ + destroy: function() { + this.db.close(); + this.db = null; + + this.jsonParser = null; + this.wktParser = null; + + OpenLayers.Protocol.SQL.prototype.destroy.apply(this); + }, + + /** + * APIMethod: supported + * Determine whether a browser supports Gears + * + * Returns: + * {Boolean} The browser supports Gears + */ + supported: function() { + return !!(window.google && google.gears); + }, + + /** + * Method: read + * Read all features from the database and return a + * instance. If the options parameter + * contains a callback attribute, the function is called with the response + * as a parameter. + * + * Parameters: + * options - {Object} Optional object for configuring the request; it + * can have the {Boolean} property "noFeatureStateReset" which + * specifies if the state of features read from the Gears + * database must be reset to null, if "noFeatureStateReset" + * is undefined or false then each feature's state is reset + * to null, if "noFeatureStateReset" is true the feature state + * is preserved. + * + * Returns: + * {} An + * object. + */ + read: function(options) { + options = OpenLayers.Util.applyDefaults(options, this.options); + + var feature, features = []; + var rs = this.db.execute("SELECT * FROM " + this.tableName); + while (rs.isValidRow()) { + feature = this.unfreezeFeature(rs); + if (this.evaluateFilter(feature, options.filter)) { + if (!options.noFeatureStateReset) { + feature.state = null; + } + features.push(feature); + } + rs.next(); + } + rs.close(); + + var resp = new OpenLayers.Protocol.Response({ + code: OpenLayers.Protocol.Response.SUCCESS, + requestType: "read", + features: features + }); + + if (options && options.callback) { + options.callback.call(options.scope, resp); + } + + return resp; + }, + + /** + * Method: unfreezeFeature + * + * Parameters: + * row - {ResultSet} + * + * Returns: + * {} + */ + unfreezeFeature: function(row) { + var feature; + var wkt = row.fieldByName('geometry'); + if (wkt == this.NULL_GEOMETRY) { + feature = new OpenLayers.Feature.Vector(); + } else { + feature = this.wktParser.read(wkt); + } + + feature.attributes = this.jsonParser.read( + row.fieldByName('properties')); + + feature.fid = this.extractFidFromField(row.fieldByName('fid')); + + var state = row.fieldByName('state'); + if (state == this.NULL_FEATURE_STATE) { + state = null; + } + feature.state = state; + + return feature; + }, + + /** + * Method: extractFidFromField + * + * Parameters: + * field - {String} + * + * Returns + * {String} or {Number} The fid. + */ + extractFidFromField: function(field) { + if (!field.match(this.fidRegExp) && this.typeOfFid == "number") { + field = parseFloat(field); + } + return field; + }, + + /** + * Method: create + * Create new features into the database. + * + * Parameters: + * features - {Array({})} or + * {} The features to create in + * the database. + * options - {Object} Optional object for configuring the request. + * + * Returns: + * {} An + * object. + */ + create: function(features, options) { + options = OpenLayers.Util.applyDefaults(options, this.options); + + var resp = this.createOrUpdate(features); + resp.requestType = "create"; + + if (options && options.callback) { + options.callback.call(options.scope, resp); + } + + return resp; + }, + + /** + * Method: update + * Construct a request updating modified feature. + * + * Parameters: + * features - {Array({})} or + * {} The features to update in + * the database. + * options - {Object} Optional object for configuring the request. + * + * Returns: + * {} An + * object. + */ + update: function(features, options) { + options = OpenLayers.Util.applyDefaults(options, this.options); + + var resp = this.createOrUpdate(features); + resp.requestType = "update"; + + if (options && options.callback) { + options.callback.call(options.scope, resp); + } + + return resp; + }, + + /** + * Method: createOrUpdate + * Construct a request for updating or creating features in the + * database. + * + * Parameters: + * features - {Array({})} or + * {} The feature to create or update + * in the database. + * + * Returns: + * {} An + * object. + */ + createOrUpdate: function(features) { + if (!(features instanceof Array)) { + features = [features]; + } + + var i, len = features.length, feature; + var insertedFeatures = new Array(len); + + for (i = 0; i < len; i++) { + feature = features[i]; + var params = this.freezeFeature(feature); + this.db.execute( + "REPLACE INTO " + this.tableName + + " (fid, geometry, properties, state)" + + " VALUES (?, ?, ?, ?)", + params); + + var clone = feature.clone(); + clone.fid = this.extractFidFromField(params[0]); + insertedFeatures[i] = clone; + } + + return new OpenLayers.Protocol.Response({ + code: OpenLayers.Protocol.Response.SUCCESS, + features: insertedFeatures, + reqFeatures: features + }); + }, + + /** + * Method: freezeFeature + * + * Parameters: + * feature - {} + * state - {String} The feature state to store in the database. + * + * Returns: + * {Array} + */ + freezeFeature: function(feature) { + // 2 notes: + // - fid might not be a string + // - getFeatureStateForFreeze needs the feature fid to it's stored + // in the feature here + feature.fid = feature.fid != null ? + "" + feature.fid : OpenLayers.Util.createUniqueID(this.FID_PREFIX); + + var geometry = feature.geometry != null ? + feature.geometry.toString() : this.NULL_GEOMETRY; + + var properties = this.jsonParser.write(feature.attributes); + + var state = this.getFeatureStateForFreeze(feature); + + return [feature.fid, geometry, properties, state]; + }, + + /** + * Method: getFeatureStateForFreeze + * Get the state of the feature to store into the database. + * + * Parameters: + * feature - {} The feature. + * + * Returns + * {String} The state + */ + getFeatureStateForFreeze: function(feature) { + var state; + if (!this.saveFeatureState) { + state = this.NULL_FEATURE_STATE; + } else if (this.createdOffline(feature)) { + // if the feature was created in offline mode, its + // state must remain INSERT + state = OpenLayers.State.INSERT; + } else { + state = feature.state; + } + return state; + }, + + /** + * Method: delete + * Delete features from the database. + * + * Parameters: + * features - {Array({})} or + * {} + * options - {Object} Optional object for configuring the request. + * This object is modified and should not be reused. + * + * Returns: + * {} An + * object. + */ + "delete": function(features, options) { + if (!(features instanceof Array)) { + features = [features]; + } + + options = OpenLayers.Util.applyDefaults(options, this.options); + + var i, len, feature; + for (i = 0, len = features.length; i < len; i++) { + feature = features[i]; + + // if saveFeatureState is set to true and if the feature wasn't created + // in offline mode we don't delete it in the database but just update + // it state column + if (this.saveFeatureState && !this.createdOffline(feature)) { + var toDelete = feature.clone(); + toDelete.fid = feature.fid; + if (toDelete.geometry) { + toDelete.geometry.destroy(); + toDelete.geometry = null; + } + toDelete.state = feature.state; + this.createOrUpdate(toDelete); + } else { + this.db.execute( + "DELETE FROM " + this.tableName + + " WHERE fid = ?", [feature.fid]); + } + } + + var resp = new OpenLayers.Protocol.Response({ + code: OpenLayers.Protocol.Response.SUCCESS, + requestType: "delete", + reqFeatures: features + }); + + if (options && options.callback) { + options.callback.call(options.scope, resp); + } + + return resp; + }, + + /** + * Method: createdOffline + * Returns true if the feature had a feature id when it was created in + * the Gears database, false otherwise; this is determined by + * checking the form of the feature's fid value. + * + * Parameters: + * feature - {} + * + * Returns: + * {Boolean} + */ + createdOffline: function(feature) { + return (typeof feature.fid == "string" && + !!(feature.fid.match(this.fidRegExp))); + }, + + /** + * Method: commit + * Go over the features and for each take action + * based on the feature state. Possible actions are create, + * update and delete. + * + * Parameters: + * features - {Array({})} + * options - {Object} Object whose possible keys are "create", "update", + * "delete", "callback" and "scope", the values referenced by the + * first three are objects as passed to the "create", "update", and + * "delete" methods, the value referenced by the "callback" key is + * a function which is called when the commit operation is complete + * using the scope referenced by the "scope" key. + * + * Returns: + * {Array({})} An array of + * objects, one per request made + * to the database. + */ + commit: function(features, options) { + var opt, resp = [], nRequests = 0, nResponses = 0; + + function callback(resp) { + if (++nResponses < nRequests) { + resp.last = false; + } + this.callUserCallback(options, resp); + } + + var feature, toCreate = [], toUpdate = [], toDelete = []; + for (var i = features.length - 1; i >= 0; i--) { + feature = features[i]; + switch (feature.state) { + case OpenLayers.State.INSERT: + toCreate.push(feature); + break; + case OpenLayers.State.UPDATE: + toUpdate.push(feature); + break; + case OpenLayers.State.DELETE: + toDelete.push(feature); + break; + } + } + if (toCreate.length > 0) { + nRequests++; + opt = OpenLayers.Util.applyDefaults( + {"callback": callback, "scope": this}, + options.create + ); + resp.push(this.create(toCreate, opt)); + } + if (toUpdate.length > 0) { + nRequests++; + opt = OpenLayers.Util.applyDefaults( + {"callback": callback, "scope": this}, + options.update + ); + resp.push(this.update(toUpdate, opt)); + } + if (toDelete.length > 0) { + nRequests++; + opt = OpenLayers.Util.applyDefaults( + {"callback": callback, "scope": this}, + options["delete"] + ); + resp.push(this["delete"](toDelete, opt)); + } + + return resp; + }, + + /** + * Method: clear + * Removes all rows of the table. + */ + clear: function() { + this.db.execute("DELETE FROM " + this.tableName); + }, + + /** + * Method: callUserCallback + * This method is called from within commit each time a request is made + * to the database, it is responsible for calling the user-supplied + * callbacks. + * + * Parameters: + * options - {Object} The map of options passed to the commit call. + * resp - {} + */ + callUserCallback: function(options, resp) { + var opt = options[resp.requestType]; + if (opt && opt.callback) { + opt.callback.call(opt.scope, resp); + } + if (resp.last && options.callback) { + options.callback.call(options.scope); + } + }, + + CLASS_NAME: "OpenLayers.Protocol.SQL.Gears" +}); diff --git a/lib/OpenLayers/Renderer/Elements.js b/lib/OpenLayers/Renderer/Elements.js index 40a21e8155..ec209e6ccb 100644 --- a/lib/OpenLayers/Renderer/Elements.js +++ b/lib/OpenLayers/Renderer/Elements.js @@ -810,7 +810,9 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, { if (this.indexer) { this.indexer.remove(element); - + } + + if (element._style.backgroundGraphic) { var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX; var bElem = OpenLayers.Util.getElement(backgroundId); if (bElem && bElem.parentNode) { diff --git a/lib/OpenLayers/Renderer/SVG.js b/lib/OpenLayers/Renderer/SVG.js index cf66273cb7..807c09973a 100644 --- a/lib/OpenLayers/Renderer/SVG.js +++ b/lib/OpenLayers/Renderer/SVG.js @@ -153,7 +153,7 @@ OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, { * y - {Float} * * Returns: - * {Boolean} true if the translation parameters ar in the valid coordinates + * {Boolean} true if the translation parameters are in the valid coordinates * range, false otherwise. */ translate: function(x, y) { diff --git a/lib/OpenLayers/Renderer/VML.js b/lib/OpenLayers/Renderer/VML.js index 76333dd971..33d773322c 100644 --- a/lib/OpenLayers/Renderer/VML.js +++ b/lib/OpenLayers/Renderer/VML.js @@ -33,6 +33,12 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { */ symbolCache: {}, + /** + * Property: offset + * {Object} Hash with "x" and "y" properties + */ + offset: null, + /** * Constructor: OpenLayers.Renderer.VML * Create a new VML renderer. @@ -50,8 +56,10 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { style.addRule('olv\\:*', "behavior: url(#default#VML); " + "position: absolute; display: inline-block;"); } + OpenLayers.Renderer.Elements.prototype.initialize.apply(this, arguments); + this.offset = {x: 0, y: 0}; }, /** @@ -90,8 +98,18 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { arguments); var resolution = this.getResolution(); - var org = extent.left/resolution + " " + - (extent.top/resolution - this.size.h); + var left = extent.left/resolution; + var top = extent.top/resolution - this.size.h; + if (resolutionChanged) { + this.offset = {x: left, y: top}; + left = 0; + top = 0; + } else { + left = left - this.offset.x; + top = top - this.offset.y; + } + + var org = left + " " + top; this.root.setAttribute("coordorigin", org); var size = this.size.w + " " + this.size.h; @@ -191,8 +209,8 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { var yOffset = (style.graphicYOffset != undefined) ? style.graphicYOffset : -(0.5 * height); - node.style.left = ((geometry.x/resolution)+xOffset).toFixed(); - node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed(); + node.style.left = ((geometry.x/resolution - this.offset.x)+xOffset).toFixed(); + node.style.top = ((geometry.y/resolution - this.offset.y)-(yOffset+height)).toFixed(); node.style.width = width + "px"; node.style.height = height + "px"; node.style.flip = "y"; @@ -448,10 +466,10 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { var resolution = this.getResolution(); var scaledBox = - new OpenLayers.Bounds((bbox.left/resolution).toFixed(), - (bbox.bottom/resolution).toFixed(), - (bbox.right/resolution).toFixed(), - (bbox.top/resolution).toFixed()); + new OpenLayers.Bounds((bbox.left/resolution - this.offset.x).toFixed(), + (bbox.bottom/resolution - this.offset.y).toFixed(), + (bbox.right/resolution - this.offset.x).toFixed(), + (bbox.top/resolution - this.offset.y).toFixed()); // Set the internal coordinate system to draw the path node.style.left = scaledBox.left + "px"; @@ -613,9 +631,9 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { drawCircle: function(node, geometry, radius) { if(!isNaN(geometry.x)&& !isNaN(geometry.y)) { var resolution = this.getResolution(); - - node.style.left = ((geometry.x /resolution).toFixed() - radius) + "px"; - node.style.top = ((geometry.y /resolution).toFixed() - radius) + "px"; + + node.style.left = ((geometry.x /resolution - this.offset.x).toFixed() - radius) + "px"; + node.style.top = ((geometry.y /resolution - this.offset.y).toFixed() - radius) + "px"; var diameter = radius * 2; @@ -680,8 +698,8 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { var comp, x, y; for (var i = 0; i < numComponents; i++) { comp = geometry.components[i]; - x = (comp.x/resolution); - y = (comp.y/resolution); + x = (comp.x/resolution - this.offset.x); + y = (comp.y/resolution - this.offset.y); parts[i] = " " + x.toFixed() + "," + y.toFixed() + " l "; } var end = (closeLine) ? " x e" : " e"; @@ -713,8 +731,8 @@ OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, { path.push("m"); for (i=0, ilen=linearRing.components.length; i 0) { + var separator = (url.indexOf('?') > -1) ? '&' : '?'; + url += separator + paramString; + } } if(config.proxy && (url.indexOf("http") == 0)) { url = config.proxy + encodeURIComponent(url); @@ -265,4 +269,4 @@ OpenLayers.Request = { return OpenLayers.Request.issue(config); } -}; \ No newline at end of file +}; diff --git a/lib/OpenLayers/Strategy/BBOX.js b/lib/OpenLayers/Strategy/BBOX.js new file mode 100644 index 0000000000..f37fd0154d --- /dev/null +++ b/lib/OpenLayers/Strategy/BBOX.js @@ -0,0 +1,210 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Strategy.js + * @requires OpenLayers/Filter/Spatial.js + */ + +/** + * Class: OpenLayers.Strategy.BBOX + * A simple strategy that reads new features when the viewport invalidates + * some bounds. + * + * Inherits from: + * - + */ +OpenLayers.Strategy.BBOX = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * Property: bounds + * {} The current data bounds. + */ + bounds: null, + + /** + * Property: ratio + * {Float} The ratio of the data bounds to the viewport bounds (in each + * dimension). + */ + ratio: 2, + + /** + * Property: response + * {} The protocol response object returned + * by the layer protocol. + */ + response: null, + + /** + * Constructor: OpenLayers.Strategy.BBOX + * Create a new BBOX strategy. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Strategy.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: activate + * Set up strategy with regard to reading new batches of remote data. + * + * Returns: + * {Boolean} The strategy was successfully activated. + */ + activate: function() { + var activated = OpenLayers.Strategy.prototype.activate.call(this); + if(activated) { + this.layer.events.on({ + "moveend": this.update, + scope: this + }); + this.layer.events.on({ + "refresh": this.update, + scope: this + }); + } + return activated; + }, + + /** + * Method: deactivate + * Tear down strategy with regard to reading new batches of remote data. + * + * Returns: + * {Boolean} The strategy was successfully deactivated. + */ + deactivate: function() { + var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); + if(deactivated) { + this.layer.events.un({ + "moveend": this.update, + scope: this + }); + this.layer.events.un({ + "refresh": this.update, + scope: this + }); + } + return deactivated; + }, + + /** + * Method: update + * Callback function called on "moveend" or "refresh" layer events. + * + * Parameters: + * options - {Object} An object with a property named "force", this + * property references a boolean value indicating if new data + * must be incondtionally read. + */ + update: function(options) { + var mapBounds = this.layer.map.getExtent(); + if ((options && options.force) || this.invalidBounds(mapBounds)) { + this.calculateBounds(mapBounds); + this.triggerRead(); + } + }, + + /** + * Method: invalidBounds + * + * Parameters: + * mapBounds - {} the current map extent, will be + * retrieved from the map object if not provided + * + * Returns: + * {Boolean} + */ + invalidBounds: function(mapBounds) { + if(!mapBounds) { + mapBounds = this.layer.map.getExtent(); + } + return !this.bounds || !this.bounds.containsBounds(mapBounds); + }, + + /** + * Method: calculateBounds + * + * Parameters: + * mapBounds - {} the current map extent, will be + * retrieved from the map object if not provided + */ + calculateBounds: function(mapBounds) { + if(!mapBounds) { + mapBounds = this.layer.map.getExtent(); + } + var center = mapBounds.getCenterLonLat(); + var dataWidth = mapBounds.getWidth() * this.ratio; + var dataHeight = mapBounds.getHeight() * this.ratio; + this.bounds = new OpenLayers.Bounds( + center.lon - (dataWidth / 2), + center.lat - (dataHeight / 2), + center.lon + (dataWidth / 2), + center.lat + (dataHeight / 2) + ); + }, + + /** + * Method: triggerRead + * + * Returns: + * {} The protocol response object + * returned by the layer protocol. + */ + triggerRead: function() { + var filter = this.createFilter(); + if (this.response && this.response.priv && + typeof this.response.priv.abort == "function") { + this.response.priv.abort(); + } + this.response = this.layer.protocol.read({ + filter: filter, + callback: this.merge, + scope: this + }); + }, + + /** + * Method: createFilter + * + * Returns + * {} The filter object. + */ + createFilter: function() { + var filter = new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.BBOX, + value: this.bounds, + projection: this.layer.projection + }); + if (this.layer.filter) { + filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.AND, + filters: [this.layer.filter, filter] + }); + } + return filter; + }, + + /** + * Method: merge + * Given a list of features, determine which ones to add to the layer. + * + * Parameters: + * resp - {} The response object passed + * by the protocol. + */ + merge: function(resp) { + this.layer.destroyFeatures(); + var features = resp.features; + if(features && features.length > 0) { + this.layer.addFeatures(features); + } + }, + + CLASS_NAME: "OpenLayers.Strategy.BBOX" +}); diff --git a/lib/OpenLayers/Strategy/Cluster.js b/lib/OpenLayers/Strategy/Cluster.js new file mode 100644 index 0000000000..80e4b283f0 --- /dev/null +++ b/lib/OpenLayers/Strategy/Cluster.js @@ -0,0 +1,261 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Strategy.js + */ + +/** + * Class: OpenLayers.Strategy.Cluster + * Strategy for vector feature clustering. + * + * Inherits from: + * - + */ +OpenLayers.Strategy.Cluster = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * Property: layer + * {} The layer that this strategy is assigned to. + */ + layer: null, + + /** + * APIProperty: distance + * {Integer} Pixel distance between features that should be considered a + * single cluster. Default is 20 pixels. + */ + distance: 20, + + /** + * Property: features + * {Array()} Cached features. + */ + features: null, + + /** + * Property: clusters + * {Array()} Calculated clusters. + */ + clusters: null, + + /** + * Property: clustering + * {Boolean} The strategy is currently clustering features. + */ + clustering: false, + + /** + * Property: resolution + * {Float} The resolution (map units per pixel) of the current cluster set. + */ + resolution: null, + + /** + * Constructor: OpenLayers.Strategy.Cluster + * Create a new clustering strategy. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Strategy.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: activate + * Activate the strategy. Register any listeners, do appropriate setup. + * + * Returns: + * {Boolean} The strategy was successfully activated. + */ + activate: function() { + var activated = OpenLayers.Strategy.prototype.activate.call(this); + if(activated) { + this.layer.events.on({ + "beforefeaturesadded": this.cacheFeatures, + scope: this + }); + this.layer.map.events.on({"zoomend": this.cluster, scope: this}); + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the strategy. Unregister any listeners, do appropriate + * tear-down. + * + * Returns: + * {Boolean} The strategy was successfully deactivated. + */ + deactivate: function() { + var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); + if(deactivated) { + this.clearCache(); + this.layer.events.un({ + "beforefeaturesadded": this.cacheFeatures, + scope: this + }); + this.layer.map.events.un({"zoomend": this.cluster, scope: this}); + } + return deactivated; + }, + + /** + * Method: cacheFeatures + * Cache features before they are added to the layer. + * + * Parameters: + * event - {Object} The event that this was listening for. This will come + * with a batch of features to be clustered. + * + * Returns: + * {Boolean} False to stop layer from being added to the layer. + */ + cacheFeatures: function(event) { + var propagate = true; + if(!this.clustering) { + this.clearCache(); + this.features = event.features; + this.cluster(); + propagate = false; + } + return propagate; + }, + + /** + * Method: clearCache + * Clear out the cached features. This destroys features, assuming + * nothing else has a reference. + */ + clearCache: function() { + if(this.features) { + for(var i=0; i 0) { + this.clustering = true; + // A legitimate feature addition could occur during this + // addFeatures call. For clustering to behave well, features + // should be removed from a layer before requesting a new batch. + this.layer.addFeatures(clusters); + this.clustering = false; + } + this.clusters = clusters; + } + } + }, + + /** + * Method: clustersExist + * Determine whether calculated clusters are already on the layer. + * + * Returns: + * {Boolean} The calculated clusters are already on the layer. + */ + clustersExist: function() { + var exist = false; + if(this.clusters && this.clusters.length > 0 && + this.clusters.length == this.layer.features.length) { + exist = true; + for(var i=0; i} A cluster. + * feature - {} A feature. + * + * Returns: + * {Boolean} The feature should be included in the cluster. + */ + shouldCluster: function(cluster, feature) { + var cc = cluster.geometry.getBounds().getCenterLonLat(); + var fc = feature.geometry.getBounds().getCenterLonLat(); + var distance = ( + Math.sqrt( + Math.pow((cc.lon - fc.lon), 2) + Math.pow((cc.lat - fc.lat), 2) + ) / this.resolution + ); + return (distance <= this.distance); + }, + + /** + * Method: addToCluster + * Add a feature to a cluster. + * + * Parameters: + * cluster - {} A cluster. + * feature - {} A feature. + */ + addToCluster: function(cluster, feature) { + cluster.cluster.push(feature); + cluster.attributes.count += 1; + }, + + /** + * Method: createCluster + * Given a feature, create a cluster. + * + * Parameters: + * feature - {} + * + * Returns: + * {} A cluster. + */ + createCluster: function(feature) { + var center = feature.geometry.getBounds().getCenterLonLat(); + var cluster = new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(center.lon, center.lat), + {count: 1} + ); + cluster.cluster = [feature]; + return cluster; + }, + + CLASS_NAME: "OpenLayers.Strategy.Cluster" +}); diff --git a/lib/OpenLayers/Strategy/Paging.js b/lib/OpenLayers/Strategy/Paging.js new file mode 100644 index 0000000000..c3ac684317 --- /dev/null +++ b/lib/OpenLayers/Strategy/Paging.js @@ -0,0 +1,241 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Strategy.js + */ + +/** + * Class: OpenLayers.Strategy.Paging + * Strategy for vector feature paging + * + * Inherits from: + * - + */ +OpenLayers.Strategy.Paging = OpenLayers.Class(OpenLayers.Strategy, { + + /** + * Property: layer + * {} The layer that this strategy is assigned to. + */ + layer: null, + + /** + * Property: features + * {Array()} Cached features. + */ + features: null, + + /** + * Property: length + * {Integer} Number of features per page. Default is 10. + */ + length: 10, + + /** + * Property: num + * {Integer} The currently displayed page number. + */ + num: null, + + /** + * Property: paging + * {Boolean} The strategy is currently changing pages. + */ + paging: false, + + /** + * Constructor: OpenLayers.Strategy.Paging + * Create a new paging strategy. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + */ + initialize: function(options) { + OpenLayers.Strategy.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: activate + * Activate the strategy. Register any listeners, do appropriate setup. + * + * Returns: + * {Boolean} The strategy was successfully activated. + */ + activate: function() { + var activated = OpenLayers.Strategy.prototype.activate.call(this); + if(activated) { + this.layer.events.on({ + "beforefeaturesadded": this.cacheFeatures, + scope: this + }); + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the strategy. Unregister any listeners, do appropriate + * tear-down. + * + * Returns: + * {Boolean} The strategy was successfully deactivated. + */ + deactivate: function() { + var deactivated = OpenLayers.Strategy.prototype.deactivate.call(this); + if(deactivated) { + this.clearCache(); + this.layer.events.un({ + "beforefeaturesadded": this.cacheFeatures, + scope: this + }); + } + return deactivated; + }, + + /** + * Method: cacheFeatures + * Cache features before they are added to the layer. + * + * Parameters: + * event - {Object} The event that this was listening for. This will come + * with a batch of features to be paged. + */ + cacheFeatures: function(event) { + if(!this.paging) { + this.clearCache(); + this.features = event.features; + this.pageNext(event); + } + }, + + /** + * Method: clearCache + * Clear out the cached features. This destroys features, assuming + * nothing else has a reference. + */ + clearCache: function() { + if(this.features) { + for(var i=0; i 0) { + this.length = newLength; + } + return this.length; + }, + + /** + * APIMethod: pageNext + * Display the next page of features. + * + * Returns: + * {Boolean} A new page was displayed. + */ + pageNext: function(event) { + var changed = false; + if(this.features) { + if(this.num === null) { + this.num = -1; + } + var start = (this.num + 1) * this.length; + changed = this.page(start, event); + } + return changed; + }, + + /** + * APIMethod: pagePrevious + * Display the previous page of features. + * + * Returns: + * {Boolean} A new page was displayed. + */ + pagePrevious: function() { + var changed = false; + if(this.features) { + if(this.num === null) { + this.num = this.pageCount(); + } + var start = (this.num - 1) * this.length; + changed = this.page(start); + } + return changed; + }, + + /** + * Method: page + * Display the page starting at the given index from the cache. + * + * Returns: + * {Boolean} A new page was displayed. + */ + page: function(start, event) { + var changed = false; + if(this.features) { + if(start >= 0 && start < this.features.length) { + var num = Math.floor(start / this.length); + if(num != this.num) { + this.paging = true; + var features = this.features.slice(start, start + this.length); + this.layer.removeFeatures(this.layer.features); + this.num = num; + // modify the event if any + if(event && event.features) { + // this.was called by an event listener + event.features = features; + } else { + // this was called directly on the strategy + this.layer.addFeatures(features); + } + this.paging = false; + changed = true; + } + } + } + return changed; + }, + + CLASS_NAME: "OpenLayers.Strategy.Paging" +}); diff --git a/lib/OpenLayers/Util.js b/lib/OpenLayers/Util.js index 0eac4b4a5e..4bf8045ef8 100644 --- a/lib/OpenLayers/Util.js +++ b/lib/OpenLayers/Util.js @@ -796,13 +796,18 @@ OpenLayers.Util.rad = function(x) {return x*Math.PI/180;}; /** * Function: distVincenty + * Given two objects representing points with geographic coordinates, this + * calculates the distance between those points on the surface of an + * ellipsoid. * * Parameters: * p1 - {} (or any object with both .lat, .lon properties) * p2 - {} (or any object with both .lat, .lon properties) * * Returns: - * {Float} + * {Float} The distance (in km) between the two input points as measured on an + * ellipsoid. Note that the input point objects must be in geographic + * coordinates (decimal degrees) and the return distance is in kilometers. */ OpenLayers.Util.distVincenty=function(p1, p2) { var a = 6378137, b = 6356752.3142, f = 1/298.257223563; diff --git a/tests/Control/Permalink.html b/tests/Control/Permalink.html index b811987a64..c42d2039d2 100644 --- a/tests/Control/Permalink.html +++ b/tests/Control/Permalink.html @@ -134,6 +134,99 @@ t.eq(this.map.controls[this.map.controls.length-1].CLASS_NAME, "CustomArgParser", "Custom ArgParser added correctly."); t.eq(control.div.firstChild.getAttribute("href"), "./edit.html?zoom=2&lat=0&lon=1.75781&layers=B&customParam=foo", "Custom parameter encoded correctly."); } + + function test_Control_Permalink_createParams(t) { + t.plan(18); + + var baseLayer = { 'isBaseLayer': true }; + + var m = { + 'getCenter': function() { return null; } + }; + + var pl = { + 'map': m, + 'base': {} + }; + + old_getParameters = OpenLayers.Util.getParameters; + OpenLayers.Util.getParameters = function(base) { + t.ok(base == pl.base, "correct base sent in to Util.getParameters()"); + return g_Params; + }; + + //null center, null map.getCenter() + g_Params = {}; + m.baseLayer = baseLayer; + var returnParams = OpenLayers.Control.Permalink.prototype.createParams.apply(pl, []); + t.ok(returnParams == g_Params, "correct params returned on null center"); + + //valid center, zoom, layers + g_Params = { 'test': {} }; + var center = { 'lon': 1.2345678901, 'lat': 9.8765432109 }; + var zoom = {}; + var layers = [ + { 'isBaseLayer': true }, + baseLayer, + { 'isBaseLayer': false, 'getVisibility': function() { return true; } }, + { 'isBaseLayer': false, 'getVisibility': function() { return false; } } + ]; + var returnParams = OpenLayers.Control.Permalink.prototype.createParams.apply(pl, [center, zoom, layers]); + + t.ok(returnParams.test == g_Params.test, "correct params returned from Util.getParameters() when valid center, zoom, layers"); + t.ok(returnParams.zoom == zoom, "params.zoom set correctly when valid center, zoom, layers"); + t.eq(returnParams.lon, 1.23457, "lon set and rounded correctly when valid center, zoom, layers"); + t.eq(returnParams.lat, 9.87654, "lat set and rounded correctly when valid center, zoom, layers"); + t.eq(returnParams.layers, "0BTF", "layers processed correctly when valid center, zoom, layers") + + + //null center, zoom, layers, with displayProjection + g_Params = { 'test': {} }; + g_Projection = {}; + m = { + 'baseLayer': baseLayer, + 'getProjectionObject': function() { return g_Projection; }, + 'center': { 'lon': {}, 'lat': {} }, + 'getCenter': function() { return this.center; }, + 'zoom': {}, + 'getZoom': function() { return this.zoom; }, + 'layers': [ + { 'isBaseLayer': false, 'getVisibility': function() { return true; } }, + baseLayer, + { 'isBaseLayer': false, 'getVisibility': function() { return false; } }, + { 'isBaseLayer': true } + ], + 'getLayers': function() { return this.layers; } + }; + pl = { + 'base': {}, + 'map': m, + 'displayProjection': {} + }; + + old_transform = OpenLayers.Projection.transform; + OpenLayers.Projection.transform = function(point, projObj, dispProj) { + t.ok(point.x = m.center.lon, "correct x value passed into transform"); + t.ok(point.y = m.center.lat, "correct x value passed into transform"); + t.ok(projObj == g_Projection, "correct projection object from map passed into transform"); + t.ok(dispProj == pl.displayProjection, "correct displayProjection from control passed into transform"); + + return { 'x': 9.8765432109, 'y': 1.2345678901 }; + }; + + center = zoom = layers = null; + + var returnParams = OpenLayers.Control.Permalink.prototype.createParams.apply(pl, [center, zoom, layers]); + t.ok(returnParams.test == g_Params.test, "correct params returned from Util.getParameters() when null center, zoom, layers, with displayProjection"); + t.ok(returnParams.zoom == m.zoom, "params.zoom set correctly when null center, zoom, layers, with displayProjection"); + t.eq(returnParams.lon, 9.87654, "lon set, transformed, and rounded correctly when null center, zoom, layers, with displayProjection"); + t.eq(returnParams.lat, 1.23457, "lat set, transformed, and rounded correctly when null center, zoom, layers, with displayProjection"); + t.eq(returnParams.layers, "TBF0", "layers processed correctly when null center, zoom, layers, with displayProjection"); + + OpenLayers.Util.getParameters = old_getParameters; + OpenLayers.Projection.transform = old_transform; + } + diff --git a/tests/Format.html b/tests/Format.html index 70a1d5fe6a..9c761dff28 100644 --- a/tests/Format.html +++ b/tests/Format.html @@ -4,7 +4,7 @@ diff --git a/tests/Format/GML/cases.js b/tests/Format/GML/cases.js new file mode 100644 index 0000000000..214e84fd71 --- /dev/null +++ b/tests/Format/GML/cases.js @@ -0,0 +1,222 @@ +var xml = new OpenLayers.Format.XML(); +function readXML(file) { + return xml.read(document.getElementById(file).firstChild.nodeValue); +} + +var cases = { + + "v2/point-coord.xml": new OpenLayers.Geometry.Point(1, 2), + + "v2/point-coordinates.xml": new OpenLayers.Geometry.Point(1, 2), + + "v2/linestring-coord.xml": new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4) + ]), + + "v2/linestring-coordinates.xml": new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4) + ]), + + "v2/linearring-coord.xml": new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + + "v2/linearring-coordinates.xml": new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + + "v2/polygon-coord.xml": new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(4, 5), + new OpenLayers.Geometry.Point(6, 7), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(7, 8), + new OpenLayers.Geometry.Point(3, 4) + ]) + ]), + + "v2/polygon-coordinates.xml": new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(4, 5), + new OpenLayers.Geometry.Point(6, 7), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(7, 8), + new OpenLayers.Geometry.Point(3, 4) + ]) + ]), + + "v2/multipoint-coord.xml": new OpenLayers.Geometry.MultiPoint([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(3, 4) + ]), + + "v2/multipoint-coordinates.xml": new OpenLayers.Geometry.MultiPoint([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(3, 4) + ]), + + "v2/multilinestring-coord.xml": new OpenLayers.Geometry.MultiLineString([ + new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(4, 5) + ]) + ]), + + "v2/multilinestring-coordinates.xml": new OpenLayers.Geometry.MultiLineString([ + new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(4, 5) + ]) + ]), + + "v2/multipolygon-coord.xml": new OpenLayers.Geometry.MultiPolygon([ + new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(4, 5), + new OpenLayers.Geometry.Point(6, 7), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(7, 8), + new OpenLayers.Geometry.Point(3, 4) + ]) + ]), + new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]) + ]) + ]), + + "v2/multipolygon-coordinates.xml": new OpenLayers.Geometry.MultiPolygon([ + new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(4, 5), + new OpenLayers.Geometry.Point(6, 7), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(7, 8), + new OpenLayers.Geometry.Point(3, 4) + ]) + ]), + new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]) + ]) + ]), + + "v2/geometrycollection-coordinates.xml": new OpenLayers.Geometry.Collection([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.LineString([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4) + ]), + new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(1, 2), + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(1, 2) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(2, 3), + new OpenLayers.Geometry.Point(4, 5), + new OpenLayers.Geometry.Point(6, 7), + new OpenLayers.Geometry.Point(2, 3) + ]), + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(3, 4), + new OpenLayers.Geometry.Point(5, 6), + new OpenLayers.Geometry.Point(7, 8), + new OpenLayers.Geometry.Point(3, 4) + ]) + ]) + ]), + + "v2/box-coord.xml": new OpenLayers.Bounds(1, 2, 3, 4), + + "v2/box-coordinates.xml": new OpenLayers.Bounds(1, 2, 3, 4) + +}; + +// cases for v3 use the same geometries +OpenLayers.Util.extend(cases, { + "v3/point.xml": cases["v2/point-coordinates.xml"], + "v3/linestring.xml": cases["v2/linestring-coordinates.xml"], + "v3/polygon.xml": cases["v2/polygon-coordinates.xml"], + "v3/multipoint-singular.xml": cases["v2/multipoint-coordinates.xml"], + "v3/multipoint-plural.xml": cases["v2/multipoint-coordinates.xml"], + "v3/multilinestring-singular.xml": cases["v2/multilinestring-coordinates.xml"], + "v3/multilinestring-plural.xml": cases["v2/multilinestring-coordinates.xml"], + "v3/multipolygon-singular.xml": cases["v2/multipolygon-coordinates.xml"], + "v3/multipolygon-plural.xml": cases["v2/multipolygon-coordinates.xml"], + "v3/multisurface-singular.xml": cases["v2/multipolygon-coordinates.xml"], + "v3/multisurface-plural.xml": cases["v2/multipolygon-coordinates.xml"], + "v3/envelope.xml": cases["v2/box-coordinates.xml"] +}); \ No newline at end of file diff --git a/tests/Format/GML/v2.html b/tests/Format/GML/v2.html new file mode 100644 index 0000000000..6684883ab8 --- /dev/null +++ b/tests/Format/GML/v2.html @@ -0,0 +1,605 @@ + + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/tests/Format/GML/v3.html b/tests/Format/GML/v3.html new file mode 100644 index 0000000000..e24a2fb0a8 --- /dev/null +++ b/tests/Format/GML/v3.html @@ -0,0 +1,551 @@ + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + diff --git a/tests/Format/XML.html b/tests/Format/XML.html index aff544e134..e58dace6e4 100644 --- a/tests/Format/XML.html +++ b/tests/Format/XML.html @@ -27,7 +27,7 @@ '<' + '/ol:root>'; function test_Format_XML_constructor(t) { - t.plan(5); + t.plan(13); var options = {'foo': 'bar'}; var format = new OpenLayers.Format.XML(options); @@ -38,6 +38,36 @@ t.eq(typeof format.write, "function", "format has a write function"); t.ok(!window.ActiveXObject || format.xmldom, "browsers with activeX must have xmldom"); + + // test namespaces + t.ok(format.namespaces instanceof Object, "format has namespace object"); + var namespaces = {"foo": "bar"}; + format = new OpenLayers.Format.XML({namespaces: namespaces}); + t.eq(format.namespaces, namespaces, "format.namespaces correctly set in constructor"); + + // test default prefix + t.eq(format.defaultPrefix, null, "defaultPrefix is null by default"); + format = new OpenLayers.Format.XML({defaultPrefix: "foo"}); + t.eq(format.defaultPrefix, "foo", "defaultPrefix correctly set in constructor"); + + // test readers + t.ok(format.readers instanceof Object, "format has readers object"); + var readers = {"foo": "bar"}; + format = new OpenLayers.Format.XML({readers: readers}); + t.eq(format.readers, readers, "format.readers correctly set in constructor"); + + // test readers + t.ok(format.writers instanceof Object, "format has writers object"); + var writers = {"foo": "bar"}; + format = new OpenLayers.Format.XML({writers: writers}); + t.eq(format.writers, writers, "format.writers correctly set in constructor"); + } + + function test_destroy(t) { + t.plan(1); + var format = new OpenLayers.Format.XML(); + format.destroy(); + t.eq(format.xmldom, null, "xmldom set to null for all browsers"); } function test_Format_XML_read(t) { @@ -260,6 +290,363 @@ found = format.hasAttributeNS(nodes[0], taUri, "nothing"); t.ok(found === false, "returns false for bad attribute"); } + + function test_namespaces(t) { + t.plan(2); + + var format = new OpenLayers.Format.XML({ + namespaces: { + "def": "http://example.com/default", + "foo": "http://example.com/foo", + "bar": "http://example.com/bar" + }, + defaultPrefix: "def" + }); + + // test that prototype has not been altered + t.eq(OpenLayers.Format.XML.prototype.namespaces, null, + "setting namespaces at construction does not modify prototype"); + + // test that namespaceAlias has been set + t.eq(format.namespaceAlias["http://example.com/foo"], "foo", + "namespaceAlias mapping has been set"); + + } + + function test_setNamespace(t) { + t.plan(3); + + var format = new OpenLayers.Format.XML(); + + // test that namespaces is an object + t.ok(format.namespaces instanceof Object, "empty namespace object set"); + + format.setNamespace("foo", "http://example.com/foo"); + t.eq(format.namespaces["foo"], "http://example.com/foo", "alias -> uri mapping set"); + t.eq(format.namespaceAlias["http://example.com/foo"], "foo", "uri -> alias mapping set"); + + } + + function test_readChildNodes(t) { + + var text = "" + + "" + + "" + + "" + + "-180" + + "90" + + "" + + "some text for first marker" + + "" + + "" + + "" + + "" + + "180" + + "-90" + + "" + + "some text for second marker" + + "" + + "" + + ""; + + var expect = [ + new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(-180, 90), + { + name: 'my marker 1', + link: 'http://host/path/1', + detail: 'some text for first marker' + } + ), + new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(180, -90), + { + name: 'my marker 2', + link: 'http://host/path/2', + detail: 'some text for second marker' + } + ) + ]; + + var format = new OpenLayers.Format.XML({ + defaultPrefix: "foo", + namespaces: { + "foo": "http://example.com/foo", + "atom": "http://www.w3.org/2005/Atom" + }, + readers: { + "foo": { + "container": function(node, obj) { + var list = []; + this.readChildNodes(node, list); + obj.list = list; + }, + "marker": function(node, list) { + var feature = new OpenLayers.Feature.Vector(); + feature.attributes.name = node.getAttribute("name"); + this.readChildNodes(node, feature); + list.push(feature); + }, + "position": function(node, feature) { + var obj = {}; + this.readChildNodes(node, obj); + feature.geometry = new OpenLayers.Geometry.Point(obj.x, obj.y); + }, + "lon": function(node, obj) { + obj.x = this.getChildValue(node); + }, + "lat": function(node, obj) { + obj.y = this.getChildValue(node); + }, + "detail": function(node, feature) { + feature.attributes.detail = this.getChildValue(node); + } + }, + "atom": { + "link": function(node, feature) { + feature.attributes.link = node.getAttribute("href"); + } + } + } + }); + + // convert text to document node + var doc = format.read(text); + // read child nodes to get back some object + var obj = format.readChildNodes(doc); + // start comparing what we got to what we expect + var got = obj.list; + + t.plan(11); + t.eq(got.length, expect.length, "correct number of items parsed"); + t.eq(got[0].geometry.x, expect[0].geometry.x, "correct x coord parsed for marker 1"); + t.eq(got[0].geometry.y, expect[0].geometry.y, "correct y coord parsed for marker 1"); + t.eq(got[0].attributes.name, expect[0].attributes.name, "correct name parsed for marker 1"); + t.eq(got[0].attributes.detail, expect[0].attributes.detail, "correct detail parsed for marker 1"); + t.eq(got[0].attributes.link, expect[0].attributes.link, "correct link parsed for marker 1"); + t.eq(got[1].geometry.x, expect[1].geometry.x, "correct x coord parsed for marker 2"); + t.eq(got[1].geometry.y, expect[1].geometry.y, "correct y coord parsed for marker 2"); + t.eq(got[1].attributes.name, expect[1].attributes.name, "correct name parsed for marker 2"); + t.eq(got[1].attributes.detail, expect[1].attributes.detail, "correct detail parsed for marker 2"); + t.eq(got[1].attributes.link, expect[1].attributes.link, "correct link parsed for marker 2"); + + } + + function test_writeNode(t) { + + var features = [ + new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(-180, 90), + { + name: 'my marker 1', + link: 'http://host/path/1', + detail: 'some text for first marker' + } + ), + new OpenLayers.Feature.Vector( + new OpenLayers.Geometry.Point(180, -90), + { + name: 'my marker 2', + link: 'http://host/path/2', + detail: 'some text for second marker' + } + ) + ]; + + var expect = "" + + "" + + "" + + "" + + "-180" + + "90" + + "" + + "some text for first marker" + + "" + + "" + + "" + + "" + + "180" + + "-90" + + "" + + "some text for second marker" + + "" + + "" + + ""; + + var format = new OpenLayers.Format.XML({ + defaultPrefix: "foo", + namespaces: { + "foo": "http://example.com/foo", + "atom": "http://www.w3.org/2005/Atom" + }, + writers: { + "foo": { + "container": function(features) { + var node = this.createElementNSPlus("container"); + var feature; + for(var i=0; i" + }, { + description: "def prefixed name with default options", + node: format.createElementNSPlus("def:FooNode"), + expect: "" + }, { + description: "foo prefixed name with default options", + node: format.createElementNSPlus("foo:FooNode"), + expect: "" + }, { + description: "unprefixed name with uri option", + node: format.createElementNSPlus("FooNode", { + uri: "http://example.com/elsewhere" + }), + expect: "" + }, { + description: "foo prefixed name with uri option (overriding format.namespaces)", + node: format.createElementNSPlus("foo:FooNode", { + uri: "http://example.com/elsewhere" + }), + expect: "" + }, { + description: "foo prefixed name with attributes option", + node: format.createElementNSPlus("foo:FooNode", { + attributes: { + "id": "123", + "foo:attr1": "namespaced attribute 1", + "bar:attr2": "namespaced attribute 2" + } + }), + expect: "" + }, { + description: "foo prefixed name with attributes and value options", + node: format.createElementNSPlus("foo:FooNode", { + attributes: {"id": "123"}, + value: "text value" + }), + expect: "text value<" + "/foo:FooNode>" + } + ]; + + t.plan(cases.length); + var test; + for(var i=0; i" + }, { + description: "foo prefixed attribute", + node: format.createElementNSPlus("foo:Node"), + attributes: {"foo:id": "123"}, + expect: "" + }, { + description: "foo prefixed attribute with def prefixed node", + node: format.createElementNSPlus("def:Node"), + attributes: {"foo:id": "123"}, + expect: "" + }, { + description: "multiple attributes", + node: format.createElementNSPlus("def:Node"), + attributes: {"id": "123", "foo": "bar"}, + expect: "" + } + ]; + + t.plan(cases.length); + var test; + for(var i=0; i diff --git a/tests/Protocol/HTTP.html b/tests/Protocol/HTTP.html index 56a27b4476..3892754ae8 100644 --- a/tests/Protocol/HTTP.html +++ b/tests/Protocol/HTTP.html @@ -102,6 +102,35 @@ }; var resp = protocol.read(readOptions); + + OpenLayers.Request.GET = _get; + } + + function test_read_bbox(t) { + t.plan(1); + var protocol = new OpenLayers.Protocol.HTTP(); + + // fake XHR request object + var request = {'status': 200}; + + var _get = OpenLayers.Request.GET; + + var bounds = new OpenLayers.Bounds(1, 2, 3, 4); + var filter = new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.BBOX, + value: bounds, + projection: "foo" + }); + + OpenLayers.Request.GET = function(options) { + t.eq(options.params['bbox'].toString(), bounds.toArray().toString(), + 'GET called with bbox filter in params'); + return request; + }; + + var resp = protocol.read({filter: filter}); + + OpenLayers.Request.GET = _get; } function test_parseFeatures(t) { @@ -217,6 +246,8 @@ }; var resp = protocol.create(features, createOptions); + + OpenLayers.Request.POST = _post; } function test_update(t) { @@ -284,8 +315,10 @@ }; var resp = protocol.update(feature, updateOptions); - + + OpenLayers.Request.PUT = _put; } + function test_handleResponse(t) { t.plan(6); @@ -404,6 +437,7 @@ var resp = protocol['delete'](feature, deleteOptions); + OpenLayers.Request.DELETE = _delete; } function test_handleDelete(t) { diff --git a/tests/Protocol/SQL.html b/tests/Protocol/SQL.html new file mode 100644 index 0000000000..01377e0dd3 --- /dev/null +++ b/tests/Protocol/SQL.html @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/tests/Protocol/SQL/Gears.html b/tests/Protocol/SQL/Gears.html new file mode 100644 index 0000000000..6f97f1a0b1 --- /dev/null +++ b/tests/Protocol/SQL/Gears.html @@ -0,0 +1,473 @@ + + + + + + + + diff --git a/tests/Renderer/Elements.html b/tests/Renderer/Elements.html index 75d07715b7..e58ccdb72b 100644 --- a/tests/Renderer/Elements.html +++ b/tests/Renderer/Elements.html @@ -428,7 +428,8 @@ 'removeChild': function(elem) { gElemRemoved = elem; } - } + }, + '_style' : {backgroundGraphic: "foo"} }; gBackElement = { 'parentNode': { diff --git a/tests/Request.html b/tests/Request.html index 28484d2406..b1807e21f7 100644 --- a/tests/Request.html +++ b/tests/Request.html @@ -20,7 +20,7 @@ function test_issue(t) { setup(); - t.plan(19); + t.plan(22); var request, config; var proto = OpenLayers.Request.XMLHttpRequest.prototype; var issue = OpenLayers.Function.bind(OpenLayers.Request.issue, @@ -46,8 +46,43 @@ t.eq(async, config.async, "open called with correct async"); t.eq(user, config.user, "open called with correct user"); t.eq(password, config.password, "open called with correct password"); + }; + request = issue(config); + + // test that params are serialized as query string - 1 test + config = { + method: "GET", + url: "http://example.com/", + params: {"foo": "bar"} + }; + proto.open = function(method, url, async, user, password) { + t.eq(url, config.url + "?foo=bar", "params serialized as query string"); + }; + request = issue(config); + + // test that empty params object doesn't produce query string - 1 test + config = { + method: "GET", + url: "http://example.com/", + params: {} + }; + proto.open = function(method, url, async, user, password) { + t.eq(url, config.url, "empty params doesn't produce query string"); } request = issue(config); + + // test that query string doesn't get two ? separators + config = { + method: "GET", + url: "http://example.com/?existing=query", + params: {"foo": "bar"} + }; + proto.open = function(method, url, async, user, password) { + t.eq(url, config.url + "&foo=bar", "existing query string gets extended with &"); + } + request = issue(config); + + // reset open method proto.open = _open; // test that headers are correctly set - 4 tests diff --git a/tests/Strategy/BBOX.html b/tests/Strategy/BBOX.html new file mode 100644 index 0000000000..bb019de260 --- /dev/null +++ b/tests/Strategy/BBOX.html @@ -0,0 +1,166 @@ + + + + + + +
+ + diff --git a/tests/Strategy/Cluster.html b/tests/Strategy/Cluster.html new file mode 100644 index 0000000000..7dd0fa6088 --- /dev/null +++ b/tests/Strategy/Cluster.html @@ -0,0 +1,108 @@ + + + + + + +
+ + diff --git a/tests/Strategy/Paging.html b/tests/Strategy/Paging.html new file mode 100644 index 0000000000..221cd5c86a --- /dev/null +++ b/tests/Strategy/Paging.html @@ -0,0 +1,113 @@ + + + + + + +
+ + diff --git a/tests/geom_eq.js b/tests/geom_eq.js new file mode 100644 index 0000000000..26f3a95034 --- /dev/null +++ b/tests/geom_eq.js @@ -0,0 +1,110 @@ +/** + * File: xml_eq.js + * Adds a xml_eq method to AnotherWay test objects. + * + */ + +(function() { + + /** + * Function assertEqual + * Test two objects for equivalence (based on ==). Throw an exception + * if not equivalent. + * + * Parameters: + * got - {Object} + * expected - {Object} + * msg - {String} The message to be thrown. This message will be appended + * with ": got {got} but expected {expected}" where got and expected are + * replaced with string representations of the above arguments. + */ + function assertEqual(got, expected, msg) { + if(got === undefined) { + got = "undefined"; + } else if (got === null) { + got = "null"; + } + if(expected === undefined) { + expected = "undefined"; + } else if (expected === null) { + expected = "null"; + } + if(got != expected) { + throw msg + ": got '" + got + "' but expected '" + expected + "'"; + } + } + + /** + * Function assertGeometryEqual + * Test two geometries for equivalence. Geometries are considered + * equivalent if they are of the same class, and given component + * geometries, if all components are equivalent. Throws a message as + * exception if not equivalent. + * + * Parameters: + * got - {OpenLayers.Geometry} + * expected - {OpenLayers.Geometry} + * options - {Object} Optional object for configuring test options. + */ + function assertGeometryEqual(got, expected, options) { + + var OpenLayers = Test.AnotherWay._g_test_iframe.OpenLayers; + + // compare types + assertEqual(typeof got, typeof expected, "Object types mismatch"); + + // compare classes + assertEqual(got.CLASS_NAME, expected.CLASS_NAME, "Object class mismatch"); + + if(got instanceof OpenLayers.Geometry.Point) { + // compare points + assertEqual(got.x, expected.x, "x mismatch"); + assertEqual(got.y, expected.y, "y mismatch"); + assertEqual(got.z, expected.z, "z mismatch"); + } else { + // compare components + assertEqual( + got.components.length, expected.components.length, + "Component length mismatch for " + got.CLASS_NAME + ); + for(var i=0; iFormat/GeoJSON.html
  • Format/GeoRSS.html
  • Format/GML.html
  • +
  • Format/GML/v2.html
  • +
  • Format/GML/v3.html
  • Format/GPX.html
  • Format/JSON.html
  • Format/KML.html
  • @@ -115,6 +117,8 @@
  • Projection.html
  • Protocol.html
  • Protocol/HTTP.html
  • +
  • Protocol/SQL.html
  • +
  • Protocol/SQL/Gears.html
  • Renderer.html
  • Renderer/Canvas.html
  • Renderer/Elements.html
  • @@ -124,7 +128,10 @@
  • Request/XMLHttpRequest.html
  • Rule.html
  • Strategy.html
  • +
  • Strategy/Cluster.html
  • Strategy/Fixed.html
  • +
  • Strategy/Paging.html
  • +
  • Strategy/BBOX.html
  • Style.html
  • StyleMap.html
  • Tile.html
  • diff --git a/tests/run-tests.html b/tests/run-tests.html index 46bea5d640..13c350b5ec 100644 --- a/tests/run-tests.html +++ b/tests/run-tests.html @@ -2307,6 +2307,7 @@ onload=function() // --> +
    diff --git a/tests/xml_eq.js b/tests/xml_eq.js index c330979945..47084cd858 100644 --- a/tests/xml_eq.js +++ b/tests/xml_eq.js @@ -97,10 +97,13 @@ * Parameters: * got - {DOMElement} * expected - {DOMElement} - * options - {Object} Optional object for configuring test options. Set - * 'prefix' property to true in order to compare element and attribute - * prefixes (namespace uri always tested). By default, prefixes - * are not tested. + * options - {Object} Optional object for configuring test options. + * + * Valid options: + * prefix - {Boolean} Compare element and attribute + * prefixes (namespace uri always tested). Default is false. + * includeWhiteSpace - {Boolean} Include whitespace only nodes when + * comparing child nodes. Default is false. */ function assertElementNodesEqual(got, expected, options) { var testPrefix = (options && options.prefix === true); @@ -190,14 +193,17 @@ } // compare children + var gotChildNodes = getChildNodes(got, options); + var expChildNodes = getChildNodes(expected, options); + assertEqual( - got.childNodes.length, expected.childNodes.length, + gotChildNodes.length, expChildNodes.length, "Children length mismatch for " + got.nodeName ); - for(var j=0; j