diff --git a/build/build.py b/build/build.py index ebb07d358f..0ca2a74a0e 100755 --- a/build/build.py +++ b/build/build.py @@ -51,10 +51,14 @@ def build(config_file = None, output_file = None, options = None): outputFilename = output_file print "Merging libraries." - if use_compressor == "closure": - sourceFiles = mergejs.getNames(sourceDirectory, configFilename) - else: - merged = mergejs.run(sourceDirectory, None, configFilename) + try: + if use_compressor == "closure": + sourceFiles = mergejs.getNames(sourceDirectory, configFilename) + else: + merged = mergejs.run(sourceDirectory, None, configFilename) + except mergejs.MissingImport, E: + print "\nAbnormal termination." + sys.exit("ERROR: %s" % E) print "Compressing using %s" % use_compressor if use_compressor == "jsmin": diff --git a/build/light.cfg b/build/light.cfg new file mode 100644 index 0000000000..f2f7a649a4 --- /dev/null +++ b/build/light.cfg @@ -0,0 +1,32 @@ +[first] + +[last] + +[include] +OpenLayers/Map.js +OpenLayers/Kinetic.js +OpenLayers/Projection.js +OpenLayers/Layer/Vector.js +OpenLayers/Layer/OSM.js +OpenLayers/Layer/Bing.js +OpenLayers/Layer/WMS.js +OpenLayers/Layer/Google/v3.js +OpenLayers/Popup/FramedCloud.js +OpenLayers/Control/Navigation.js +OpenLayers/Control/ZoomPanel.js +OpenLayers/Control/Attribution.js +OpenLayers/Control/SelectFeature.js +OpenLayers/Control/Panel.js +OpenLayers/Control/LayerSwitcher.js +OpenLayers/Renderer/SVG.js +OpenLayers/Renderer/VML.js +OpenLayers/Format/GeoJSON.js +OpenLayers/Protocol/HTTP.js +OpenLayers/Strategy/Fixed.js +OpenLayers/Strategy/BBOX.js +OpenLayers/StyleMap.js +OpenLayers/Rule.js +OpenLayers/Filter/Comparison.js +OpenLayers/Filter/Logical.js + +[exclude] diff --git a/build/mobile.cfg b/build/mobile.cfg index 0fe32aaa6a..7bba251270 100644 --- a/build/mobile.cfg +++ b/build/mobile.cfg @@ -1,5 +1,4 @@ [first] -OpenLayers/SingleFile.js [last] diff --git a/doc/walkthru.html b/doc/walkthru.html deleted file mode 100644 index b135e63523..0000000000 --- a/doc/walkthru.html +++ /dev/null @@ -1,96 +0,0 @@ - - -OpenLayers examples walkthrough - - - - -

<ol>

-

API documentation: http://dev.openlayers.org/docs/

-

Examples: http://openlayers.org/dev/examples/

-

Maps

-
    -
  1. Basic example
  2. -
  3. Layer switcher (aka legend)
  4. -
  5. Base layers versus overlays
  6. -
  7. Multiple WMS mirrors
  8. -
  9. Full screen map
  10. -
  11. Wrapping the date line
  12. -
  13. Other cartographic projections
  14. -
  15. Translucent overlays
  16. -
- -

Raster Layers

-
    -
  1. Untiled WMS layer
  2. -
  3. Ka-Map layer
  4. -
  5. MapServer layer
  6. -
  7. Worldwind layer
  8. -
  9. TMS layer
  10. -
  11. Image layer
  12. -
  13. Google layer
  14. -
  15. VirtualEarth layer
  16. -
  17. Spherical Mercator ("EPSG:900913")
  18. -
  19. TileCache
  20. -
- -

Marker Layers

-
    -
  1. Markers
  2. -
  3. Changing marker properties dynamically
  4. -
  5. Popups
  6. -
  7. GeoRSS (example data)
  8. -
  9. GeoRSS with custom markers
  10. -
  11. Point layer from WFS
  12. -
  13. WFS GetFeatureInfo example
  14. -
- -

Events and Controls

-
    -
  1. Navigation tools on the map
  2. -
  3. Navigation toolbar off the map
  4. -
  5. Tracking the mouse position
  6. -
  7. Overview map
  8. -
  9. Layer attribution
  10. -
  11. Full range of controls
  12. -
  13. Custom controls #1
  14. -
  15. Custom controls #2
  16. -
  17. Custom control styles
  18. -
  19. Trapping click events
  20. -
  21. Tracking map events
  22. -
- -

Vector Layers

-
    -
  1. Generating features in JavaScript
  2. -
  3. Loading features from GML
  4. -
  5. Loading features from KML
  6. -
  7. Serializing features to GML
  8. -
  9. Serializing to other formats
  10. -
  11. Selecting features
  12. -
  13. Attaching popups to features
  14. -
- -

Editing Tools

-
    -
  1. Drawing features
  2. -
  3. Editing toolbar (outside the map)
  4. -
  5. Creating regular polygons
  6. -
  7. Modifying features
  8. -
  9. Resizing features
  10. -
  11. Rotating features
  12. -
  13. Transactional WFS example
  14. -
  15. FeatureServer
  16. -
- -

Testing

-
    -
  1. Test.AnotherWay suite
  2. -
- -

</ol>

- - diff --git a/examples/accessible-click-control.html b/examples/accessible-click-control.html new file mode 100644 index 0000000000..c8d97cde88 --- /dev/null +++ b/examples/accessible-click-control.html @@ -0,0 +1,69 @@ + + + + + + + Accessible Custom Click Control + + + + + + + + + +

An accessible click control implementation

+ +
+ click, control, accessibility +
+ + + Jump to map + + +
+ +

+ Demonstrate the KeyboardDefaults control as well as a control that + allows clicking on the map using the keyboard. + First focus the map (using tab key or mouse), then press the 'i' + key to activate the query control. You can then move the point + using arrow keys. Press 'RETURN' to get the coordinate. Press 'i' + again to deactivate the control. +

+ + + diff --git a/examples/accessible-click-control.js b/examples/accessible-click-control.js new file mode 100644 index 0000000000..328e0da768 --- /dev/null +++ b/examples/accessible-click-control.js @@ -0,0 +1,199 @@ +var map, navigationControl, queryControl; + +function init(){ + map = new OpenLayers.Map('map', {controls: []}); + var layer = new OpenLayers.Layer.WMS( + "OpenLayers WMS", + "http://vmap0.tiles.osgeo.org/wms/vmap0", + {layers: 'basic'} + ); + map.addLayers([layer]); + + navigationControl = new OpenLayers.Control.KeyboardDefaults({ + observeElement: 'map' + }); + map.addControl(navigationControl); + + queryControl = new OpenLayers.Control.KeyboardClick({ + observeElement: 'map' + }); + map.addControl(queryControl); + + map.zoomToMaxExtent(); +} + +/** + * Class: OpenLayers.Control.KeyboardClick + * + * A custom control that (a) adds a vector point that can be moved using the + * arrow keys of the keyboard, and (b) displays a browser alert window when the + * RETURN key is pressed. The control can be activated/deactivated using the + * "i" key. When activated the control deactivates any KeyboardDefaults control + * in the map so that the map is not moved when the arrow keys are pressed. + * + * This control relies on the OpenLayers.Handler.KeyboardPoint custom handler. + */ +OpenLayers.Control.KeyboardClick = OpenLayers.Class(OpenLayers.Control, { + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + var observeElement = this.observeElement || document; + this.handler = new OpenLayers.Handler.KeyboardPoint(this, { + done: this.onClick, + cancel: this.deactivate + }, { + observeElement: observeElement + }); + OpenLayers.Event.observe( + observeElement, + "keydown", + OpenLayers.Function.bindAsEventListener( + function(evt) { + if (evt.keyCode == 73) { // "i" + if (this.active) { + this.deactivate(); + } else { + this.activate(); + } + } + }, + this + ) + ); + }, + + onClick: function(geometry) { + alert("You clicked near " + geometry.x + " N, " + + geometry.y + " E"); + }, + + activate: function() { + if(!OpenLayers.Control.prototype.activate.apply(this, arguments)) { + return false; + } + // deactivate any KeyboardDefaults control + var keyboardDefaults = this.map.getControlsByClass( + 'OpenLayers.Control.KeyboardDefaults')[0]; + if (keyboardDefaults) { + keyboardDefaults.deactivate(); + } + return true; + }, + + deactivate: function() { + if(!OpenLayers.Control.prototype.deactivate.apply(this, arguments)) { + return false; + } + // reactivate any KeyboardDefaults control + var keyboardDefaults = this.map.getControlsByClass( + 'OpenLayers.Control.KeyboardDefaults')[0]; + if (keyboardDefaults) { + keyboardDefaults.activate(); + } + return true; + } +}); + +/** + * Class: OpenLayers.Handler.KeyboardPoint + * + * A custom handler that displays a vector point that can be moved + * using the arrow keys of the keyboard. + */ +OpenLayers.Handler.KeyboardPoint = OpenLayers.Class(OpenLayers.Handler, { + + KEY_EVENTS: ["keydown"], + + + initialize: function(control, callbacks, options) { + OpenLayers.Handler.prototype.initialize.apply(this, arguments); + // cache the bound event listener method so it can be unobserved later + this.eventListener = OpenLayers.Function.bindAsEventListener( + this.handleKeyEvent, this + ); + }, + + activate: function() { + if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) { + return false; + } + this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME); + this.map.addLayer(this.layer); + this.observeElement = this.observeElement || document; + for (var i=0, len=this.KEY_EVENTS.length; i + + + + + + Custom and accessible panel + + + + + + + +

Custom and accessible panel

+
+ panels, CSS, style, accessibility, button +
+

+ Create a custom and accessible panel, styled entirely with + CSS. +

+
+
+ +
+ +

An accessible panel: + +

+

+ +

By default a panel creates buttons as divs. In this example the + createControlMarkup panel function is overridden to create + a more accessible markup for the buttons. See the accessible-panel.js + source to see how this is done.

+ +

Note: in IE 8, when a button is pressed its content shifts by 1 pixel. + This is a known + IE8 bug, with known workarounds. No workaround is applied in this + example though.

+ +
+ + + diff --git a/examples/accessible-panel.js b/examples/accessible-panel.js new file mode 100644 index 0000000000..f982fc624b --- /dev/null +++ b/examples/accessible-panel.js @@ -0,0 +1,64 @@ +var lon = 5; +var lat = 40; +var zoom = 5; +var map, layer; + +function init() { + map = new OpenLayers.Map( 'map', { controls: [] } ); + layer = new OpenLayers.Layer.WMS( "OpenLayers WMS", + "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic'} ); + map.addLayer(layer); + + vlayer = new OpenLayers.Layer.Vector( "Editable" ); + map.addLayer(vlayer); + + zb = new OpenLayers.Control.ZoomBox({ + title: "Zoom box: zoom clicking and dragging", + text: "Zoom" + }); + + var panel = new OpenLayers.Control.Panel({ + defaultControl: zb, + createControlMarkup: function(control) { + var button = document.createElement('button'), + iconSpan = document.createElement('span'), + textSpan = document.createElement('span'); + iconSpan.innerHTML = ' '; + button.appendChild(iconSpan); + if (control.text) { + textSpan.innerHTML = control.text; + } + button.appendChild(textSpan); + return button; + } + }); + + panel.addControls([ + zb, + new OpenLayers.Control.DrawFeature(vlayer, OpenLayers.Handler.Path, + {title:'Draw a feature', text: 'Draw'}), + new OpenLayers.Control.ZoomToMaxExtent({ + title:"Zoom to the max extent", + text: "World" + }) + ]); + + nav = new OpenLayers.Control.NavigationHistory({ + previousOptions: { + title: "Go to previous map position", + text: "Prev" + }, + nextOptions: { + title: "Go to next map position", + text: "Next" + }, + displayClass: "navHistory" + }); + // parent control must be added to the map + map.addControl(nav); + panel.addControls([nav.next, nav.previous]); + + map.addControl(panel); + + map.setCenter(new OpenLayers.LonLat(lon, lat), zoom); +} diff --git a/examples/accessible.html b/examples/accessible.html index d9b8a777b3..36236d5b97 100644 --- a/examples/accessible.html +++ b/examples/accessible.html @@ -31,13 +31,26 @@ font-size:1em; text-decoration:underline; } + a.accesskey { + color: white; + } + a.accesskey:focus { + color: #436976; + } + a.zoom { + padding-right: 20px; + } + diff --git a/examples/cross-origin-xml.html b/examples/cross-origin-xml.html new file mode 100644 index 0000000000..b811bf72c1 --- /dev/null +++ b/examples/cross-origin-xml.html @@ -0,0 +1,32 @@ + + + + OpenLayers Script Protocol XML Example + + + + + + + + +

Script Protocol With XML

+
+ protocol, script, cross origin, xml, advanced +
+

+ Demonstrates how, with a custom parseFeatures method, the script protocol can be used with YQL for cross-origin loading of files in any of the XML formats supported by OpenLayers. +

+
+
+

+ YQL can wrap a jsonp callback around an XML file, which effectively means Yahoo's servers are acting as a proxy for cross-origin feature loading. This example uses a GPX file, but the same technique can be used for other formats such as KML. +

+

+ View the cross-origin-xml.js + source to see how this is done +

+
+ + + diff --git a/examples/cross-origin-xml.js b/examples/cross-origin-xml.js new file mode 100644 index 0000000000..a97cc1fdb6 --- /dev/null +++ b/examples/cross-origin-xml.js @@ -0,0 +1,25 @@ +var map = new OpenLayers.Map({ + div: "map", + layers: [ + new OpenLayers.Layer.OSM(), + new OpenLayers.Layer.Vector("Vectors", { + projection: new OpenLayers.Projection("EPSG:4326"), + strategies: [new OpenLayers.Strategy.Fixed()], + protocol: new OpenLayers.Protocol.Script({ + url: "http://query.yahooapis.com/v1/public/yql", + params: { + q: "select * from xml where url='http://www.topografix.com/fells_loop.gpx'" + }, + format: new OpenLayers.Format.GPX(), + parseFeatures: function(data) { + return this.format.read(data.results[0]); + } + }), + eventListeners: { + "featuresadded": function () { + this.map.zoomToExtent(this.getDataExtent()); + } + } + }) + ] +}); diff --git a/examples/google-v3.html b/examples/google-v3.html index c074bb62f5..849d7cd273 100644 --- a/examples/google-v3.html +++ b/examples/google-v3.html @@ -8,7 +8,7 @@ - + diff --git a/examples/gutter.html b/examples/gutter.html index 1d16e504c0..3dd4dbf082 100644 --- a/examples/gutter.html +++ b/examples/gutter.html @@ -45,7 +45,7 @@

Gutter Example

"http://demo.opengeo.org/geoserver/wms", {layers: 'topp:states'}, {gutter: 15}); - var states = new OpenLayers.Layer.WMS( "Roads (no gutter)", + var states = new OpenLayers.Layer.WMS( "States (no gutter)", "http://demo.opengeo.org/geoserver/wms", {layers: 'topp:states'}); map.addLayers([states, states15]); diff --git a/examples/mapbox.html b/examples/mapbox.html new file mode 100644 index 0000000000..3ccbffe776 --- /dev/null +++ b/examples/mapbox.html @@ -0,0 +1,73 @@ + + + + + + + OpenLayers MapBox Example + + + + + +

Basic MapBox OSM Example

+
mapbox xyz osm
+ +
Shows how to use MapBox tiles in an OpenLayers map.
+ +
+ +
+

This example demonstrates the use of an XYZ layer that accesses tiles from MapBox.

+

+ See the mapbox.js source + for details. Make sure to read the Terms of Service + before using MapBox tiles in your application. +

+
+ + + + diff --git a/examples/mapbox.js b/examples/mapbox.js new file mode 100644 index 0000000000..ee57d66e0c --- /dev/null +++ b/examples/mapbox.js @@ -0,0 +1,34 @@ +var streets = new OpenLayers.Layer.XYZ( + "MapBox Streets", + [ + "http://a.tiles.mapbox.com/v3/mapbox.mapbox-streets/${z}/${x}/${y}.png", + "http://b.tiles.mapbox.com/v3/mapbox.mapbox-streets/${z}/${x}/${y}.png", + "http://c.tiles.mapbox.com/v3/mapbox.mapbox-streets/${z}/${x}/${y}.png", + "http://d.tiles.mapbox.com/v3/mapbox.mapbox-streets/${z}/${x}/${y}.png" + ], { + attribution: "Tiles © MapBox | " + + "Data © OpenStreetMap " + + "and contributors, CC-BY-SA", + sphericalMercator: true, + transitionEffect: "resize", + buffer: 1, + numZoomLevels: 16 + } +); + +var map = new OpenLayers.Map({ + div: "map", + layers: [streets], + controls: [ + new OpenLayers.Control.Attribution(), + new OpenLayers.Control.Navigation({ + dragPanOptions: { + enableKinetic: true + } + }), + new OpenLayers.Control.ZoomPanel(), + new OpenLayers.Control.Permalink({anchor: true}) + ], + center: [0, 0], + zoom: 1 +}); diff --git a/examples/osm-google.html b/examples/osm-google.html index 3578246ccb..39c31dd910 100644 --- a/examples/osm-google.html +++ b/examples/osm-google.html @@ -8,7 +8,7 @@ - + diff --git a/examples/spherical-mercator.html b/examples/spherical-mercator.html index ea6c2246d5..409a8e232f 100644 --- a/examples/spherical-mercator.html +++ b/examples/spherical-mercator.html @@ -22,7 +22,7 @@ } - + diff --git a/examples/zoom.html b/examples/zoom.html new file mode 100644 index 0000000000..7b15297d05 --- /dev/null +++ b/examples/zoom.html @@ -0,0 +1,68 @@ + + + + + + + OpenLayers Zoom Example + + + + + +

Zoom Control Example

+
zoom control
+ +
Shows how to use a simple zoom control.
+ +
+

The map above uses the default control configuration and style.

+

The map below uses the custom zoom elements and styling.

+
+
+ in + out +
+
+ +
+

This example demonstrates the use of a Zoom control.

+

+ See the zoom.js source + for details. +

+
+ + + + diff --git a/examples/zoom.js b/examples/zoom.js new file mode 100644 index 0000000000..08694ccad4 --- /dev/null +++ b/examples/zoom.js @@ -0,0 +1,34 @@ +var map = new OpenLayers.Map({ + div: "map", + layers: [new OpenLayers.Layer.OSM()], + controls: [ + new OpenLayers.Control.Navigation({ + dragPanOptions: { + enableKinetic: true + } + }), + new OpenLayers.Control.Attribution(), + new OpenLayers.Control.Zoom() + ], + center: [0, 0], + zoom: 1 +}); + +var map2 = new OpenLayers.Map({ + div: "map2", + layers: [new OpenLayers.Layer.OSM()], + controls: [ + new OpenLayers.Control.Navigation({ + dragPanOptions: { + enableKinetic: true + } + }), + new OpenLayers.Control.Attribution(), + new OpenLayers.Control.Zoom({ + zoomInId: "customZoomIn", + zoomOutId: "customZoomOut" + }) + ], + center: [0, 0], + zoom: 1 +}); diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index c257ce73c4..70e695dcb0 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -207,6 +207,7 @@ "OpenLayers/Control/TransformFeature.js", "OpenLayers/Control/UTFGrid.js", "OpenLayers/Control/SLDSelect.js", + "OpenLayers/Control/Zoom.js", "OpenLayers/Geometry.js", "OpenLayers/Geometry/Collection.js", "OpenLayers/Geometry/Point.js", @@ -272,6 +273,10 @@ "OpenLayers/Format/KML.js", "OpenLayers/Format/GeoRSS.js", "OpenLayers/Format/WFS.js", + "OpenLayers/Format/OWSCommon.js", + "OpenLayers/Format/OWSCommon/v1.js", + "OpenLayers/Format/OWSCommon/v1_0_0.js", + "OpenLayers/Format/OWSCommon/v1_1_0.js", "OpenLayers/Format/WFSCapabilities.js", "OpenLayers/Format/WFSCapabilities/v1.js", "OpenLayers/Format/WFSCapabilities/v1_0_0.js", diff --git a/lib/OpenLayers/BaseTypes.js b/lib/OpenLayers/BaseTypes.js index ce7e291923..d2e5711ca8 100644 --- a/lib/OpenLayers/BaseTypes.js +++ b/lib/OpenLayers/BaseTypes.js @@ -3,6 +3,10 @@ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ +/** + * @requires OpenLayers/SingleFile.js + */ + /** * Header: OpenLayers Base Types * OpenLayers custom string, number and function functions are described here. diff --git a/lib/OpenLayers/BaseTypes/Date.js b/lib/OpenLayers/BaseTypes/Date.js index 9af051c8b6..3b645fe852 100644 --- a/lib/OpenLayers/BaseTypes/Date.js +++ b/lib/OpenLayers/BaseTypes/Date.js @@ -3,6 +3,10 @@ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ +/** + * @requires OpenLayers/SingleFile.js + */ + /** * Namespace: OpenLayers.Date * Contains implementations of Date.parse and date.toISOString that match the diff --git a/lib/OpenLayers/Control/KeyboardDefaults.js b/lib/OpenLayers/Control/KeyboardDefaults.js index f3505353f0..2589269590 100644 --- a/lib/OpenLayers/Control/KeyboardDefaults.js +++ b/lib/OpenLayers/Control/KeyboardDefaults.js @@ -34,6 +34,15 @@ OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, { */ slideFactor: 75, + /** + * APIProperty: observeElement + * {DOMelement|String} The DOM element to handle keys for. You + * can use the map div here, to have the navigation keys + * work when the map div has the focus. If undefined the + * document is used. + */ + observeElement: null, + /** * Constructor: OpenLayers.Control.KeyboardDefaults */ @@ -43,8 +52,11 @@ OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, { * Create handler. */ draw: function() { - this.handler = new OpenLayers.Handler.Keyboard( this, { - "keydown": this.defaultKeyPress }); + var observeElement = this.observeElement || document; + this.handler = new OpenLayers.Handler.Keyboard( this, + {"keydown": this.defaultKeyPress}, + {observeElement: observeElement} + ); }, /** @@ -62,7 +74,7 @@ OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, { * evt - {Event} */ defaultKeyPress: function (evt) { - var size; + var size, handled = true; switch(evt.keyCode) { case OpenLayers.Event.KEY_LEFT: this.map.pan(-this.slideFactor, 0); @@ -106,7 +118,14 @@ OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, { case 95: // -/_ (some ASCII) this.map.zoomOut(); break; - } + default: + handled = false; + } + if (handled) { + // prevent browser default not to move the page + // when moving the page with the keyboard + OpenLayers.Event.stop(evt); + } }, CLASS_NAME: "OpenLayers.Control.KeyboardDefaults" diff --git a/lib/OpenLayers/Control/LayerSwitcher.js b/lib/OpenLayers/Control/LayerSwitcher.js index a0e480e385..b1f2cc49cb 100644 --- a/lib/OpenLayers/Control/LayerSwitcher.js +++ b/lib/OpenLayers/Control/LayerSwitcher.js @@ -120,7 +120,7 @@ OpenLayers.Control.LayerSwitcher = initialize: function(options) { OpenLayers.Control.prototype.initialize.apply(this, arguments); this.layerStates = []; - + if(this.roundedCorner) { OpenLayers.Console.warn('roundedCorner option is deprecated'); } @@ -143,6 +143,7 @@ OpenLayers.Control.LayerSwitcher = changebaselayer: this.redraw, scope: this }); + this.events.unregister("buttonclick", this, this.onButtonClick); OpenLayers.Control.prototype.destroy.apply(this, arguments); }, @@ -157,13 +158,18 @@ OpenLayers.Control.LayerSwitcher = OpenLayers.Control.prototype.setMap.apply(this, arguments); this.map.events.on({ - buttonclick: this.onButtonClick, addlayer: this.redraw, changelayer: this.redraw, removelayer: this.redraw, changebaselayer: this.redraw, scope: this }); + if (this.outsideViewport) { + this.events.attachToElement(this.div); + this.events.register("buttonclick", this, this.onButtonClick); + } else { + this.map.events.register("buttonclick", this, this.onButtonClick); + } }, /** diff --git a/lib/OpenLayers/Control/Measure.js b/lib/OpenLayers/Control/Measure.js index 7eb05a02c2..0c6a057150 100644 --- a/lib/OpenLayers/Control/Measure.js +++ b/lib/OpenLayers/Control/Measure.js @@ -231,8 +231,8 @@ OpenLayers.Control.Measure = OpenLayers.Class(OpenLayers.Control, { * mouseposition. feature - {} The sketch feature. */ measureImmediate : function(point, feature, drawing) { - if (drawing && this.delayedTrigger === null && - !this.handler.freehandMode(this.handler.evt)) { + if (drawing && !this.handler.freehandMode(this.handler.evt)) { + this.cancelDelay(); this.measure(feature.geometry, "measurepartial"); } }, diff --git a/lib/OpenLayers/Control/ModifyFeature.js b/lib/OpenLayers/Control/ModifyFeature.js index 6e78184efc..30d6fad0cd 100644 --- a/lib/OpenLayers/Control/ModifyFeature.js +++ b/lib/OpenLayers/Control/ModifyFeature.js @@ -246,7 +246,6 @@ OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { // configure the drag control var dragOptions = { geometryTypes: ["OpenLayers.Geometry.Point"], - snappingOptions: this.snappingOptions, onStart: function(feature, pixel) { control.dragStart.apply(control, [feature, pixel]); }, diff --git a/lib/OpenLayers/Control/Panel.js b/lib/OpenLayers/Control/Panel.js index b04f6c3863..f141671445 100644 --- a/lib/OpenLayers/Control/Panel.js +++ b/lib/OpenLayers/Control/Panel.js @@ -245,26 +245,58 @@ OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, { controls = [controls]; } this.controls = this.controls.concat(controls); - - // Give each control a panel_div which will be used later. - // Access to this div is via the panel_div attribute of the - // control added to the panel. - // Also, stop mousedowns and clicks, but don't stop mouseup, - // since they need to pass through. + for (var i=0, len=controls.length; i} The control to create the HTML + * markup for. + * + * Returns: + * {DOMElement} The markup. + */ + createControlMarkup: function(control) { + return document.createElement("div"); + }, /** * Method: addControlsToMap diff --git a/lib/OpenLayers/Control/Permalink.js b/lib/OpenLayers/Control/Permalink.js index 5686f1f8b6..4a2112c552 100644 --- a/lib/OpenLayers/Control/Permalink.js +++ b/lib/OpenLayers/Control/Permalink.js @@ -94,12 +94,13 @@ OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, { * APIMethod: destroy */ destroy: function() { - if (this.element.parentNode == this.div) { + if (this.element && this.element.parentNode == this.div) { this.div.removeChild(this.element); + this.element = null; + } + if (this.map) { + this.map.events.unregister('moveend', this, this.updateLink); } - this.element = null; - - this.map.events.unregister('moveend', this, this.updateLink); OpenLayers.Control.prototype.destroy.apply(this, arguments); }, diff --git a/lib/OpenLayers/Control/WMSGetFeatureInfo.js b/lib/OpenLayers/Control/WMSGetFeatureInfo.js index 9348a9d542..86e9423060 100644 --- a/lib/OpenLayers/Control/WMSGetFeatureInfo.js +++ b/lib/OpenLayers/Control/WMSGetFeatureInfo.js @@ -341,6 +341,7 @@ OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, { service: "WMS", version: firstLayer.params.VERSION, request: "GetFeatureInfo", + exceptions: firstLayer.params.EXCEPTIONS, bbox: this.map.getExtent().toBBOX(null, firstLayer.reverseAxisOrder()), feature_count: this.maxFeatures, diff --git a/lib/OpenLayers/Control/Zoom.js b/lib/OpenLayers/Control/Zoom.js new file mode 100644 index 0000000000..eb212eeb83 --- /dev/null +++ b/lib/OpenLayers/Control/Zoom.js @@ -0,0 +1,139 @@ +/* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Events/buttonclick.js + */ + +/** + * Class: OpenLayers.Control.Zoom + * The Zoom control is a pair of +/- links for zooming in and out. + * + * Inherits from: + * - + */ +OpenLayers.Control.Zoom = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: zoomInText + * {String} + * Text for zoom-in link. Default is "+". + */ + zoomInText: "+", + + /** + * APIProperty: zoomInId + * {String} + * Instead of having the control create a zoom in link, you can provide + * the identifier for an anchor element already added to the document. + * By default, an element with id "olZoomInLink" will be searched for + * and used if it exists. + */ + zoomInId: "olZoomInLink", + + /** + * APIProperty: zoomOutText + * {String} + * Text for zoom-out link. Default is "-". + */ + zoomOutText: "-", + + /** + * APIProperty: zoomOutId + * {String} + * Instead of having the control create a zoom out link, you can provide + * the identifier for an anchor element already added to the document. + * By default, an element with id "olZoomOutLink" will be searched for + * and used if it exists. + */ + zoomOutId: "olZoomOutLink", + + /** + * Method: draw + * + * Returns: + * {DOMElement} A reference to the DOMElement containing the zoom links. + */ + draw: function() { + var div = OpenLayers.Control.prototype.draw.apply(this), + links = this.getOrCreateLinks(div), + zoomIn = links.zoomIn, + zoomOut = links.zoomOut, + bind = OpenLayers.Function.bind, + eventsInstance = this.map.events; + + if (zoomOut.parentNode !== div) { + eventsInstance = this.events; + eventsInstance.attachToElement(zoomOut.parentNode); + } + eventsInstance.register("buttonclick", this, this.onZoomClick); + + this.zoomInLink = zoomIn; + this.zoomOutLink = zoomOut; + return div; + }, + + /** + * Method: getOrCreateLinks + * + * Parameters: + * el - {DOMElement} + * + * Return: + * {Object} Object with zoomIn and zoomOut properties referencing links. + */ + getOrCreateLinks: function(el) { + var zoomIn = document.getElementById(this.zoomInId), + zoomOut = document.getElementById(this.zoomOutId); + if (!zoomIn) { + zoomIn = document.createElement("a"); + zoomIn.href = "#zoomIn"; + zoomIn.appendChild(document.createTextNode(this.zoomInText)); + zoomIn.className = "olControlZoomIn"; + el.appendChild(zoomIn); + } + OpenLayers.Element.addClass(zoomIn, "olButton"); + if (!zoomOut) { + zoomOut = document.createElement("a"); + zoomOut.href = "#zoomOut"; + zoomOut.appendChild(document.createTextNode(this.zoomOutText)); + zoomOut.className = "olControlZoomOut"; + el.appendChild(zoomOut); + } + OpenLayers.Element.addClass(zoomOut, "olButton"); + return { + zoomIn: zoomIn, zoomOut: zoomOut + }; + }, + + /** + * Method: onZoomClick + * Called when zoomin/out link is clicked. + */ + onZoomClick: function(evt) { + var button = evt.buttonElement; + if (button === this.zoomInLink) { + this.map.zoomIn(); + } else if (button === this.zoomOutLink) { + this.map.zoomOut(); + } + }, + + /** + * Method: destroy + * Clean up. + */ + destroy: function() { + if (this.map) { + this.map.events.unregister("buttonclick", this, this.onZoomClick); + } + delete this.zoomInLink; + delete this.zoomOutLink; + OpenLayers.Control.prototype.destroy.apply(this); + }, + + CLASS_NAME: "OpenLayers.Control.Zoom" +}); diff --git a/lib/OpenLayers/Events.js b/lib/OpenLayers/Events.js index 907ced8109..ea46bf3cba 100644 --- a/lib/OpenLayers/Events.js +++ b/lib/OpenLayers/Events.js @@ -20,6 +20,12 @@ OpenLayers.Event = { * element._eventCacheID */ observers: false, + + /** + * Constant: KEY_SPACE + * {int} + */ + KEY_SPACE: 32, /** * Constant: KEY_BACKSPACE @@ -388,7 +394,8 @@ OpenLayers.Events = OpenLayers.Class({ "mousedown", "mouseup", "mousemove", "click", "dblclick", "rightclick", "dblrightclick", "resize", "focus", "blur", - "touchstart", "touchmove", "touchend" + "touchstart", "touchmove", "touchend", + "keydown" ], /** diff --git a/lib/OpenLayers/Events/buttonclick.js b/lib/OpenLayers/Events/buttonclick.js index 351248bf0c..6520b83a0d 100644 --- a/lib/OpenLayers/Events/buttonclick.js +++ b/lib/OpenLayers/Events/buttonclick.js @@ -39,7 +39,7 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({ */ events: [ 'mousedown', 'mouseup', 'click', 'dblclick', - 'touchstart', 'touchmove', 'touchend' + 'touchstart', 'touchmove', 'touchend', 'keydown' ], /** @@ -97,6 +97,31 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({ delete this.target; }, + /** + * Method: getPressedButton + * Get the pressed button, if any. Returns undefined if no button + * was pressed. + * + * Arguments: + * element - {DOMElement} The event target. + * + * Returns: + * {DOMElement} The button element, or undefined. + */ + getPressedButton: function(element) { + var depth = 3, // limit the search depth + button; + do { + if(OpenLayers.Element.hasClass(element, "olButton")) { + // hit! + button = element; + break; + } + element = element.parentNode; + } while(--depth > 0 && element); + return button; + }, + /** * Method: buttonClick * Check if a button was clicked, and fire the buttonclick event @@ -108,15 +133,25 @@ OpenLayers.Events.buttonclick = OpenLayers.Class({ var propagate = true, element = OpenLayers.Event.element(evt); if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) { - if (element.nodeType === 3 || OpenLayers.Element.hasClass(element, "olAlphaImg")) { - element = element.parentNode; - } - if (OpenLayers.Element.hasClass(element, "olButton")) { - if (this.startEvt) { + // was a button pressed? + var button = this.getPressedButton(element); + if (button) { + if (evt.type === "keydown") { + switch (evt.keyCode) { + case OpenLayers.Event.KEY_RETURN: + case OpenLayers.Event.KEY_SPACE: + this.target.triggerEvent("buttonclick", { + buttonElement: button + }); + OpenLayers.Event.stop(evt); + propagate = false; + break; + } + } else if (this.startEvt) { if (this.completeRegEx.test(evt.type)) { - var pos = OpenLayers.Util.pagePosition(element); + var pos = OpenLayers.Util.pagePosition(button); this.target.triggerEvent("buttonclick", { - buttonElement: element, + buttonElement: button, buttonXY: { x: this.startEvt.clientX - pos[0], y: this.startEvt.clientY - pos[1] diff --git a/lib/OpenLayers/Format/KML.js b/lib/OpenLayers/Format/KML.js index 1754fd8eb5..b814eece5a 100644 --- a/lib/OpenLayers/Format/KML.js +++ b/lib/OpenLayers/Format/KML.js @@ -64,9 +64,25 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { * APIProperty: extractAttributes * {Boolean} Extract attributes from KML. Default is true. * Extracting styleUrls requires this to be set to true + * Note that currently only Data and SimpleData + * elements are handled. */ extractAttributes: true, + /** + * APIProperty: kvpAttributes + * {Boolean} Only used if extractAttributes is true. + * If set to true, attributes will be simple + * key-value pairs, compatible with other formats, + * Any displayName elements will be ignored. + * If set to false, attributes will be objects, + * retaining any displayName elements, but not + * compatible with other formats. Any CDATA in + * displayName will be read in as a string value. + * Default is false. + */ + kvpAttributes: false, + /** * Property: extractStyles * {Boolean} Extract styles from KML. Default is false. @@ -1078,12 +1094,16 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { var valueNode = data.getElementsByTagName("value"); if (valueNode.length) { ed['value'] = this.getChildValue(valueNode[0]); - } - var nameNode = data.getElementsByTagName("displayName"); - if (nameNode.length) { - ed['displayName'] = this.getChildValue(nameNode[0]); } - attributes[key] = ed; + if (this.kvpAttributes) { + attributes[key] = ed['value']; + } else { + var nameNode = data.getElementsByTagName("displayName"); + if (nameNode.length) { + ed['displayName'] = this.getChildValue(nameNode[0]); + } + attributes[key] = ed; + } } var simpleDataNodes = node.getElementsByTagName("SimpleData"); for (i = 0, len = simpleDataNodes.length; i < len; i++) { @@ -1091,8 +1111,12 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { data = simpleDataNodes[i]; key = data.getAttribute("name"); ed['value'] = this.getChildValue(data); - ed['displayName'] = key; - attributes[key] = ed; + if (this.kvpAttributes) { + attributes[key] = ed['value']; + } else { + ed['displayName'] = key; + attributes[key] = ed; + } } return attributes; @@ -1209,7 +1233,14 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { var geometryNode = this.buildGeometryNode(feature.geometry); placemarkNode.appendChild(geometryNode); - // TBD - deal with remaining (non name/description) attributes. + // output attributes as extendedData + if (feature.attributes) { + var edNode = this.buildExtendedData(feature.attributes); + if (edNode) { + placemarkNode.appendChild(edNode); + } + } + return placemarkNode; }, @@ -1440,5 +1471,48 @@ OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { return point.x + "," + point.y; }, + /** + * Method: buildExtendedData + * + * Parameters: + * attributes - {Object} + * + * Returns + * {DOMElement} A KML ExtendedData node or {null} if no attributes. + */ + buildExtendedData: function(attributes) { + var extendedData = this.createElementNS(this.kmlns, "ExtendedData"); + for (var attributeName in attributes) { + // empty, name, description, styleUrl attributes ignored + if (attributes[attributeName] && attributeName != "name" && attributeName != "description" && attributeName != "styleUrl") { + var data = this.createElementNS(this.kmlns, "Data"); + data.setAttribute("name", attributeName); + var value = this.createElementNS(this.kmlns, "value"); + if (typeof attributes[attributeName] == "object") { + // cater for object attributes with 'value' properties + // other object properties will output an empty node + if (attributes[attributeName].value) { + value.appendChild(this.createTextNode(attributes[attributeName].value)); + } + if (attributes[attributeName].displayName) { + var displayName = this.createElementNS(this.kmlns, "displayName"); + // displayName always written as CDATA + displayName.appendChild(this.getXMLDoc().createCDATASection(attributes[attributeName].displayName)); + data.appendChild(displayName); + } + } else { + value.appendChild(this.createTextNode(attributes[attributeName])); + } + data.appendChild(value); + extendedData.appendChild(data); + } + } + if (this.isSimpleContent(extendedData)) { + return null; + } else { + return extendedData; + } + }, + CLASS_NAME: "OpenLayers.Format.KML" }); diff --git a/lib/OpenLayers/Format/SLD/v1.js b/lib/OpenLayers/Format/SLD/v1.js index a6fe969c4a..71855cbee0 100644 --- a/lib/OpenLayers/Format/SLD/v1.js +++ b/lib/OpenLayers/Format/SLD/v1.js @@ -220,6 +220,78 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { ); } }, + "LabelPlacement": function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + "PointPlacement": function(node, symbolizer) { + var config = {}; + this.readChildNodes(node, config); + config.labelRotation = config.rotation; + delete config.rotation; + var labelAlign, + x = symbolizer.labelAnchorPointX, + y = symbolizer.labelAnchorPointY; + if (x <= 1/3) { + labelAlign = 'l'; + } else if (x > 1/3 && x < 2/3) { + labelAlign = 'c'; + } else if (x >= 2/3) { + labelAlign = 'r'; + } + if (y <= 1/3) { + labelAlign += 'b'; + } else if (y > 1/3 && y < 2/3) { + labelAlign += 'm'; + } else if (y >= 2/3) { + labelAlign += 't'; + } + config.labelAlign = labelAlign; + OpenLayers.Util.applyDefaults(symbolizer, config); + }, + "AnchorPoint": function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + "AnchorPointX": function(node, symbolizer) { + var labelAnchorPointX = this.readers.ogc._expression.call(this, node); + // always string, could be empty string + if(labelAnchorPointX) { + symbolizer.labelAnchorPointX = labelAnchorPointX; + } + }, + "AnchorPointY": function(node, symbolizer) { + var labelAnchorPointY = this.readers.ogc._expression.call(this, node); + // always string, could be empty string + if(labelAnchorPointY) { + symbolizer.labelAnchorPointY = labelAnchorPointY; + } + }, + "Displacement": function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + "DisplacementX": function(node, symbolizer) { + var labelXOffset = this.readers.ogc._expression.call(this, node); + // always string, could be empty string + if(labelXOffset) { + symbolizer.labelXOffset = labelXOffset; + } + }, + "DisplacementY": function(node, symbolizer) { + var labelYOffset = this.readers.ogc._expression.call(this, node); + // always string, could be empty string + if(labelYOffset) { + symbolizer.labelYOffset = labelYOffset; + } + }, + "LinePlacement": function(node, symbolizer) { + this.readChildNodes(node, symbolizer); + }, + "PerpendicularOffset": function(node, symbolizer) { + var labelPerpendicularOffset = this.readers.ogc._expression.call(this, node); + // always string, could be empty string + if(labelPerpendicularOffset) { + symbolizer.labelPerpendicularOffset = labelPerpendicularOffset; + } + }, "Label": function(node, symbolizer) { var value = this.readers.ogc._expression.call(this, node); if (value) { @@ -481,7 +553,7 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { * Method: getGraphicFormat * Given a href for an external graphic, try to determine the mime-type. * This method doesn't try too hard, and will fall back to - * if one of the known is not + * if one of the known is not * the file extension of the provided href. * * Parameters: @@ -498,7 +570,7 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { break; } } - return format || this.defautlGraphicFormat; + return format || this.defaultGraphicFormat; }, /** @@ -885,16 +957,26 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { } // add in optional Font if(symbolizer.fontFamily != null || - symbolizer.fontSize != null || - symbolizer.fontWeight != null || - symbolizer.fontStyle != null) { - this.writeNode("Font", symbolizer, node); + symbolizer.fontSize != null || + symbolizer.fontWeight != null || + symbolizer.fontStyle != null) { + this.writeNode("Font", symbolizer, node); + } + // add in optional LabelPlacement + if (symbolizer.labelAnchorPointX != null || + symbolizer.labelAnchorPointY != null || + symbolizer.labelAlign != null || + symbolizer.labelXOffset != null || + symbolizer.labelYOffset != null || + symbolizer.labelRotation != null || + symbolizer.labelPerpendicularOffset != null) { + this.writeNode("LabelPlacement", symbolizer, node); } // add in optional Halo if(symbolizer.haloRadius != null || - symbolizer.haloColor != null || - symbolizer.haloOpacity != null) { - this.writeNode("Halo", symbolizer, node); + symbolizer.haloColor != null || + symbolizer.haloOpacity != null) { + this.writeNode("Halo", symbolizer, node); } // add in optional Fill if(symbolizer.fontColor != null || @@ -906,6 +988,111 @@ OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0, { } return node; }, + "LabelPlacement": function(symbolizer) { + var node = this.createElementNSPlus("sld:LabelPlacement"); + // PointPlacement and LinePlacement are choices, so don't output both + if ((symbolizer.labelAnchorPointX != null || + symbolizer.labelAnchorPointY != null || + symbolizer.labelAlign != null || + symbolizer.labelXOffset != null || + symbolizer.labelYOffset != null || + symbolizer.labelRotation != null) && + symbolizer.labelPerpendicularOffset == null) { + this.writeNode("PointPlacement", symbolizer, node); + } + if (symbolizer.labelPerpendicularOffset != null) { + this.writeNode("LinePlacement", symbolizer, node); + } + return node; + }, + "LinePlacement": function(symbolizer) { + var node = this.createElementNSPlus("sld:LinePlacement"); + this.writeNode("PerpendicularOffset", symbolizer.labelPerpendicularOffset, node); + return node; + }, + "PerpendicularOffset": function(value) { + return this.createElementNSPlus("sld:PerpendicularOffset", { + value: value + }); + }, + "PointPlacement": function(symbolizer) { + var node = this.createElementNSPlus("sld:PointPlacement"); + if (symbolizer.labelAnchorPointX != null || + symbolizer.labelAnchorPointY != null || + symbolizer.labelAlign != null) { + this.writeNode("AnchorPoint", symbolizer, node); + } + if (symbolizer.labelXOffset != null || + symbolizer.labelYOffset != null) { + this.writeNode("Displacement", symbolizer, node); + } + if (symbolizer.labelRotation != null) { + this.writeNode("Rotation", symbolizer.labelRotation, node); + } + return node; + }, + "AnchorPoint": function(symbolizer) { + var node = this.createElementNSPlus("sld:AnchorPoint"); + var x = symbolizer.labelAnchorPointX, + y = symbolizer.labelAnchorPointY; + if (x != null) { + this.writeNode("AnchorPointX", x, node); + } + if (y != null) { + this.writeNode("AnchorPointY", y, node); + } + if (x == null && y == null) { + var xAlign = symbolizer.labelAlign.substr(0, 1), + yAlign = symbolizer.labelAlign.substr(1, 1); + if (xAlign === "l") { + x = 0; + } else if (xAlign === "c") { + x = 0.5; + } else if (xAlign === "r") { + x = 1; + } + if (yAlign === "b") { + y = 0; + } else if (yAlign === "m") { + y = 0.5; + } else if (yAlign === "t") { + y = 1; + } + this.writeNode("AnchorPointX", x, node); + this.writeNode("AnchorPointY", y, node); + } + return node; + }, + "AnchorPointX": function(value) { + return this.createElementNSPlus("sld:AnchorPointX", { + value: value + }); + }, + "AnchorPointY": function(value) { + return this.createElementNSPlus("sld:AnchorPointY", { + value: value + }); + }, + "Displacement": function(symbolizer) { + var node = this.createElementNSPlus("sld:Displacement"); + if (symbolizer.labelXOffset != null) { + this.writeNode("DisplacementX", symbolizer.labelXOffset, node); + } + if (symbolizer.labelYOffset != null) { + this.writeNode("DisplacementY", symbolizer.labelYOffset, node); + } + return node; + }, + "DisplacementX": function(value) { + return this.createElementNSPlus("sld:DisplacementX", { + value: value + }); + }, + "DisplacementY": function(value) { + return this.createElementNSPlus("sld:DisplacementY", { + value: value + }); + }, "Font": function(symbolizer) { var node = this.createElementNSPlus("sld:Font"); // add in CssParameters diff --git a/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js b/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js index 49596be1c4..bee661311b 100644 --- a/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js +++ b/lib/OpenLayers/Format/SLD/v1_0_0_GeoServer.js @@ -58,12 +58,9 @@ OpenLayers.Format.SLD.v1_0_0_GeoServer = OpenLayers.Class( }, "VendorOption": function(node, obj) { if (!obj.vendorOptions) { - obj.vendorOptions = []; + obj.vendorOptions = {}; } - obj.vendorOptions.push({ - name: node.getAttribute("name"), - value: this.getChildValue(node) - }); + obj.vendorOptions[node.getAttribute("name")] = this.getChildValue(node); } }, OpenLayers.Format.SLD.v1_0_0.prototype.readers["sld"]) }, OpenLayers.Format.SLD.v1_0_0.prototype.readers), @@ -130,8 +127,11 @@ OpenLayers.Format.SLD.v1_0_0_GeoServer = OpenLayers.Class( addVendorOptions: function(node, symbolizer) { var options = symbolizer.vendorOptions; if (options) { - for (var i=0, ii=options.length; i */ OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class( - OpenLayers.Format.WFSCapabilities, { + OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + wfs: "http://www.opengis.net/wfs", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + ows: "http://www.opengis.net/ows" + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "wfs", /** * Constructor: OpenLayers.Format.WFSCapabilities.v1_1 @@ -25,10 +41,6 @@ OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class( * options - {Object} An optional object whose properties will be set on * this instance. */ - initialize: function(options) { - OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); - this.options = options; - }, /** * APIMethod: read @@ -44,83 +56,64 @@ OpenLayers.Format.WFSCapabilities.v1 = OpenLayers.Class( if(typeof data == "string") { data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); } + var raw = data; + if(data && data.nodeType == 9) { + data = data.documentElement; + } var capabilities = {}; - var root = data.documentElement; - this.runChildNodes(capabilities, root); + this.readNode(data, capabilities); return capabilities; }, - + /** - * Method: runChildNodes + * 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. */ - runChildNodes: function(obj, node) { - var children = node.childNodes; - var childNode, processor; - for(var i=0; i 0) { + obj.featureNS = this.lookupNamespaceURI(node, parts[0]); + } + } + }, + "Title": function(node, obj) { + var title = this.getChildValue(node); + if(title) { + obj.title = title; + } + }, + "Abstract": function(node, obj) { + var abst = this.getChildValue(node); + if(abst) { + obj["abstract"] = abst; } } } }, - - /** - * Method: read_cap_FeatureTypeList - */ - read_cap_FeatureTypeList: function(request, node) { - var featureTypeList = { - featureTypes: [] - }; - this.runChildNodes(featureTypeList, node); - request.featureTypeList = featureTypeList; - }, - - /** - * Method: read_cap_FeatureType - */ - read_cap_FeatureType: function(featureTypeList, node, parentLayer) { - var featureType = {}; - this.runChildNodes(featureType, node); - featureTypeList.featureTypes.push(featureType); - }, - - /** - * Method: read_cap_Name - */ - read_cap_Name: function(obj, node) { - var name = this.getChildValue(node); - if(name) { - var parts = name.split(":"); - obj.name = parts.pop(); - if(parts.length > 0) { - obj.featureNS = this.lookupNamespaceURI(node, parts[0]); - } - } - }, - - /** - * Method: read_cap_Title - */ - read_cap_Title: function(obj, node) { - var title = this.getChildValue(node); - if(title) { - obj.title = title; - } - }, - /** - * Method: read_cap_Abstract - */ - read_cap_Abstract: function(obj, node) { - var abst = this.getChildValue(node); - if(abst) { - obj["abstract"] = abst; - } - }, - CLASS_NAME: "OpenLayers.Format.WFSCapabilities.v1" }); diff --git a/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js b/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js index 8b86487b65..0dc41f67fb 100644 --- a/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js +++ b/lib/OpenLayers/Format/WFSCapabilities/v1_0_0.js @@ -25,136 +25,89 @@ OpenLayers.Format.WFSCapabilities.v1_0_0 = OpenLayers.Class( * options - {Object} An optional object whose properties will be set on * this instance. */ - - /** - * Method: read_cap_Service - */ - read_cap_Service: function(capabilities, node) { - var service = {}; - this.runChildNodes(service, node); - capabilities.service = service; - }, /** - * Method: read_cap_Fees - */ - read_cap_Fees: function(service, node) { - var fees = this.getChildValue(node); - if (fees && fees.toLowerCase() != "none") { - service.fees = fees; - } - }, - - /** - * Method: read_cap_AccessConstraints - */ - read_cap_AccessConstraints: function(service, node) { - var constraints = this.getChildValue(node); - if (constraints && constraints.toLowerCase() != "none") { - service.accessConstraints = constraints; - } - }, - - /** - * Method: read_cap_OnlineResource - */ - read_cap_OnlineResource: function(service, node) { - var onlineResource = this.getChildValue(node); - if (onlineResource && onlineResource.toLowerCase() != "none") { - service.onlineResource = onlineResource; - } - }, - - /** - * Method: read_cap_Keywords - */ - read_cap_Keywords: function(service, node) { - var keywords = this.getChildValue(node); - if (keywords && keywords.toLowerCase() != "none") { - service.keywords = keywords.split(', '); - } - }, - - /** - * Method: read_cap_Capability - */ - read_cap_Capability: function(capabilities, node) { - var capability = {}; - this.runChildNodes(capability, node); - capabilities.capability = capability; - }, - - /** - * Method: read_cap_Request - */ - read_cap_Request: function(obj, node) { - var request = {}; - this.runChildNodes(request, node); - obj.request = request; - }, - - /** - * Method: read_cap_GetFeature + * 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. */ - read_cap_GetFeature: function(request, node) { - var getfeature = { - href: {}, // DCPType - formats: [] // ResultFormat - }; - this.runChildNodes(getfeature, node); - request.getfeature = getfeature; - }, - - /** - * Method: read_cap_ResultFormat - */ - read_cap_ResultFormat: function(obj, node) { - var children = node.childNodes; - var childNode; - for(var i=0; i} For layers with a gutter, the image offset - * represents displacement due to the gutter. - */ - imageOffset: null, - // OPTIONS /** @@ -693,7 +686,7 @@ OpenLayers.Layer = OpenLayers.Class({ /** * APIMethod: setTileSize * Set the tile size based on the map size. This also sets layer.imageSize - * and layer.imageOffset for use by Tile.Image. + * or use by Tile.Image. * * Parameters: * size - {} @@ -710,8 +703,6 @@ OpenLayers.Layer = OpenLayers.Class({ // this.name + ": layers with " + // "gutters need non-null tile sizes"); //} - this.imageOffset = new OpenLayers.Pixel(-this.gutter, - -this.gutter); this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter), tileSize.h + (2*this.gutter)); } diff --git a/lib/OpenLayers/Layer/ArcGISCache.js b/lib/OpenLayers/Layer/ArcGISCache.js index 9c500e4533..27173392c4 100644 --- a/lib/OpenLayers/Layer/ArcGISCache.js +++ b/lib/OpenLayers/Layer/ArcGISCache.js @@ -452,7 +452,9 @@ OpenLayers.Layer.ArcGISCache = OpenLayers.Class(OpenLayers.Layer.XYZ, { // Write the values into our formatted url url = OpenLayers.String.format(url, {'x': x, 'y': y, 'z': z}); - return url; + return OpenLayers.Util.urlAppend( + url, OpenLayers.Util.getParameterString(this.params) + ); }, /** diff --git a/lib/OpenLayers/Layer/Grid.js b/lib/OpenLayers/Layer/Grid.js index 1e767f75dd..e84411cee9 100644 --- a/lib/OpenLayers/Layer/Grid.js +++ b/lib/OpenLayers/Layer/Grid.js @@ -202,6 +202,10 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { * track of the loading progress. Listeners are called with an object * with a tile property as first argument, making the loded tile * available to the listener. + * tileerror - Triggered before the tileloaded event (i.e. when the tile is + * still hidden) if a tile failed to load. Listeners receive an object + * as first argument, which has a tile property that references the + * tile that could not be loaded. */ /** @@ -969,7 +973,6 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { } this.numLoadingTiles++; }; - tile.events.register("loadstart", this, tile.onLoadStart); tile.onLoadEnd = function() { this.numLoadingTiles--; @@ -987,8 +990,18 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { } } }; - tile.events.register("loadend", this, tile.onLoadEnd); - tile.events.register("unload", this, tile.onLoadEnd); + + tile.onLoadError = function() { + this.events.triggerEvent("tileerror", {tile: tile}); + }; + + tile.events.on({ + "loadstart": tile.onLoadStart, + "loadend": tile.onLoadEnd, + "unload": tile.onLoadEnd, + "loaderror": tile.onLoadError, + scope: this + }); }, /** @@ -1005,6 +1018,7 @@ OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, { "loadstart": tile.onLoadStart, "loadend": tile.onLoadEnd, "unload": tile.onLoadEnd, + "loaderror": tile.onLoadError, scope: this }); }, diff --git a/lib/OpenLayers/Layer/HTTPRequest.js b/lib/OpenLayers/Layer/HTTPRequest.js index 7855cb6b03..d1e9bf2219 100644 --- a/lib/OpenLayers/Layer/HTTPRequest.js +++ b/lib/OpenLayers/Layer/HTTPRequest.js @@ -60,7 +60,9 @@ OpenLayers.Layer.HTTPRequest = OpenLayers.Class(OpenLayers.Layer, { initialize: function(name, url, params, options) { OpenLayers.Layer.prototype.initialize.apply(this, [name, options]); this.url = url; - this.params = OpenLayers.Util.extend( {}, params); + if (!this.params) { + this.params = OpenLayers.Util.extend({}, params); + } }, /** diff --git a/lib/OpenLayers/Layer/OSM.js b/lib/OpenLayers/Layer/OSM.js index 04e7b04454..264687644f 100644 --- a/lib/OpenLayers/Layer/OSM.js +++ b/lib/OpenLayers/Layer/OSM.js @@ -34,7 +34,7 @@ OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { /** * APIProperty: url * {String} The tileset URL scheme. Defaults to - * : http://tile.openstreetmap.org/${z}/${x}/${y}.png + * : http://[a|b|c].tile.openstreetmap.org/${z}/${x}/${y}.png * (the official OSM tileset) if the second argument to the constructor * is null or undefined. To use another tileset you can have something * like this: @@ -43,7 +43,11 @@ OpenLayers.Layer.OSM = OpenLayers.Class(OpenLayers.Layer.XYZ, { * "http://tah.openstreetmap.org/Tiles/tile/${z}/${x}/${y}.png"); * (end) */ - url: 'http://tile.openstreetmap.org/${z}/${x}/${y}.png', + url: [ + 'http://a.tile.openstreetmap.org/${z}/${x}/${y}.png', + 'http://b.tile.openstreetmap.org/${z}/${x}/${y}.png', + 'http://c.tile.openstreetmap.org/${z}/${x}/${y}.png' + ], /** * Property: attribution diff --git a/lib/OpenLayers/Map.js b/lib/OpenLayers/Map.js index 15b3bb03dd..016c4110b8 100644 --- a/lib/OpenLayers/Map.js +++ b/lib/OpenLayers/Map.js @@ -239,6 +239,12 @@ OpenLayers.Map = OpenLayers.Class({ */ panRatio: 1.5, + /** + * APIProperty: options + * {Object} The options object passed to the class constructor. Read-only. + */ + options: null, + // Options /** @@ -480,6 +486,9 @@ OpenLayers.Map = OpenLayers.Class({ this.theme = OpenLayers._getScriptLocation() + 'theme/default/style.css'; + // backup original options + this.options = OpenLayers.Util.extend({}, options); + // now override default options OpenLayers.Util.extend(this, options); @@ -487,13 +496,16 @@ OpenLayers.Map = OpenLayers.Class({ this.projection.projCode : this.projection; OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]); - // allow extents to be arrays + // allow extents and center to be arrays if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) { this.maxExtent = new OpenLayers.Bounds(this.maxExtent); } if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) { this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent); } + if (this.center && !(this.center instanceof OpenLayers.LonLat)) { + this.center = new OpenLayers.LonLat(this.center); + } // initialize layers array this.layers = []; @@ -616,9 +628,9 @@ OpenLayers.Map = OpenLayers.Class({ * be properly set below. */ delete this.center; - this.addLayers(options.layers); + this.addLayers(options.layers); // set center (and optionally zoom) - if (options.center) { + if (options.center && !this.getCenter()) { // zoom can be undefined here this.setCenter(options.center, options.zoom); } @@ -731,6 +743,7 @@ OpenLayers.Map = OpenLayers.Class({ this.events.destroy(); this.events = null; + this.options = null; }, /** diff --git a/lib/OpenLayers/Popup.js b/lib/OpenLayers/Popup.js index 36f666e73f..774ecb8c03 100644 --- a/lib/OpenLayers/Popup.js +++ b/lib/OpenLayers/Popup.js @@ -685,7 +685,9 @@ OpenLayers.Popup = OpenLayers.Class({ // 'img' properties in the context. // var onImgLoad = function() { - + if (this.popup.id === null) { // this.popup has been destroyed! + return; + } this.popup.updateSize(); if ( this.popup.visible() && this.popup.panMapIfOutOfView ) { diff --git a/lib/OpenLayers/Protocol/HTTP.js b/lib/OpenLayers/Protocol/HTTP.js index ebfacac705..d5c822c7d2 100644 --- a/lib/OpenLayers/Protocol/HTTP.js +++ b/lib/OpenLayers/Protocol/HTTP.js @@ -62,12 +62,27 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { scope: null, /** - * Property: readWithPOST + * APIProperty: readWithPOST * {Boolean} true if read operations are done with POST requests * instead of GET, defaults to false. */ readWithPOST: false, + /** + * APIProperty: updateWithPOST + * {Boolean} true if update operations are done with POST requests + * defaults to false. + */ + updateWithPOST: false, + + /** + * APIProperty: deleteWithPOST + * {Boolean} true if delete operations are done with POST requests + * defaults to false. + * if true, POST data is set to output of format.write(). + */ + deleteWithPOST: false, + /** * Property: wildcarded. * {Boolean} If true percent signs are added around values @@ -99,7 +114,7 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { * Valid options include: * url - {String} * headers - {Object} - * params - {Object} + * params - {Object} URL parameters for GET requests * format - {} * callback - {Function} * scope - {Object} @@ -293,7 +308,8 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { requestType: "update" }); - resp.priv = OpenLayers.Request.PUT({ + var method = this.updateWithPOST ? "POST" : "PUT"; + resp.priv = OpenLayers.Request[method]({ url: url, callback: this.createCallback(this.handleUpdate, resp, options), headers: options.headers, @@ -344,11 +360,16 @@ OpenLayers.Protocol.HTTP = OpenLayers.Class(OpenLayers.Protocol, { requestType: "delete" }); - resp.priv = OpenLayers.Request.DELETE({ + var method = this.deleteWithPOST ? "POST" : "DELETE"; + var requestOptions = { url: url, callback: this.createCallback(this.handleDelete, resp, options), headers: options.headers - }); + }; + if (this.deleteWithPOST) { + requestOptions.data = this.format.write(feature); + } + resp.priv = OpenLayers.Request[method](requestOptions); return resp; }, diff --git a/lib/OpenLayers/Protocol/Script.js b/lib/OpenLayers/Protocol/Script.js index 49e332b7f0..62f7840bb4 100644 --- a/lib/OpenLayers/Protocol/Script.js +++ b/lib/OpenLayers/Protocol/Script.js @@ -54,20 +54,12 @@ OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { callback: null, /** - * APIProperty: scope - * {Object} Optional ``this`` object for the callback. Read-only, set - * through the options passed to the constructor. + * APIProperty: callbackTemplate + * {String} Template for creating a unique callback function name + * for the registry. Should include ${id}. + * Default is "OpenLayers.Protocol.Script.registry[${id}]". */ - scope: null, - - /** - * APIProperty: format - * {} Format for parsing features. Default is an - * format. If an alternative is provided, - * the format's read method must take an object and return an array - * of features. - */ - format: null, + callbackTemplate: "OpenLayers.Protocol.Script.registry[${id}]", /** * APIProperty: callbackKey @@ -88,6 +80,22 @@ OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { */ callbackPrefix: "", + /** + * APIProperty: scope + * {Object} Optional ``this`` object for the callback. Read-only, set + * through the options passed to the constructor. + */ + scope: null, + + /** + * APIProperty: format + * {} Format for parsing features. Default is an + * format. If an alternative is provided, + * the format's read method must take an object and return an array + * of features. + */ + format: null, + /** * Property: pendingRequests * {Object} References all pending requests. Property names are script @@ -135,7 +143,7 @@ OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { }); this.filterToParams = function(filter, params) { return format.write(filter, params); - } + }; } }, @@ -212,7 +220,7 @@ OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { */ createRequest: function(url, params, callback) { var id = OpenLayers.Protocol.Script.register(callback); - var name = "OpenLayers.Protocol.Script.registry[" + id + "]"; + var name = OpenLayers.String.format(this.callbackTemplate, {id: id}); params = OpenLayers.Util.extend({}, params); params[this.callbackKey] = this.callbackPrefix + name; url = OpenLayers.Util.urlAppend( @@ -333,7 +341,7 @@ OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { (function() { var o = OpenLayers.Protocol.Script; var counter = 0; - o.registry = []; + o.registry = {}; /** * Function: OpenLayers.Protocol.Script.register @@ -345,12 +353,11 @@ OpenLayers.Protocol.Script = OpenLayers.Class(OpenLayers.Protocol, { * that is the JSON returned by the service. * * Returns: - * {Number} An identifier for retreiving the registered callback. + * {Number} An identifier for retrieving the registered callback. */ o.register = function(callback) { - var id = ++counter; + var id = "c"+(++counter); o.registry[id] = function() { - o.unregister(id); callback.apply(this, arguments); }; return id; diff --git a/lib/OpenLayers/Renderer.js b/lib/OpenLayers/Renderer.js index 45de3a6f3f..3d7ffa1596 100644 --- a/lib/OpenLayers/Renderer.js +++ b/lib/OpenLayers/Renderer.js @@ -415,3 +415,18 @@ OpenLayers.Renderer.defaultSymbolizer = { labelAlign: 'cm' }; + + +/** + * Constant: OpenLayers.Renderer.symbol + * Coordinate arrays for well known (named) symbols. + */ +OpenLayers.Renderer.symbol = { + "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301, + 303,215, 231,161, 321,161, 350,75], + "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4, + 4,0], + "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0], + "square": [0,0, 0,1, 1,1, 1,0, 0,0], + "triangle": [0,10, 10,10, 5,0, 0,10] +}; \ No newline at end of file diff --git a/lib/OpenLayers/Renderer/Canvas.js b/lib/OpenLayers/Renderer/Canvas.js index 35939afdd2..74ae40b768 100644 --- a/lib/OpenLayers/Renderer/Canvas.js +++ b/lib/OpenLayers/Renderer/Canvas.js @@ -434,28 +434,6 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { } }, - /** - * Method: setCanvasStyle - * Prepare the canvas for drawing by setting various global settings. - * - * Parameters: - * type - {String} one of 'stroke', 'fill', or 'reset' - * style - {Object} Symbolizer hash - */ - setCanvasStyle: function(type, style) { - if (type === "fill") { - this.canvas.globalAlpha = style['fillOpacity']; - this.canvas.fillStyle = style['fillColor']; - } else if (type === "stroke") { - this.canvas.globalAlpha = style['strokeOpacity']; - this.canvas.strokeStyle = style['strokeColor']; - this.canvas.lineWidth = style['strokeWidth']; - } else { - this.canvas.globalAlpha = 0; - this.canvas.lineWidth = 1; - } - }, - /** * Method: featureIdToHex * Convert a feature ID string into an RGB hex string. @@ -787,7 +765,8 @@ OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, { */ getFeatureIdFromEvent: function(evt) { var featureId, feature; - if (this.hitDetection) { + + if (this.hitDetection && this.root.style.display !== "none") { // this dragging check should go in the feature handler if (!this.map.dragging) { var xy = evt.xy; diff --git a/lib/OpenLayers/Renderer/Elements.js b/lib/OpenLayers/Renderer/Elements.js index 7e1d89e5b1..11735f4eeb 100644 --- a/lib/OpenLayers/Renderer/Elements.js +++ b/lib/OpenLayers/Renderer/Elements.js @@ -1051,17 +1051,3 @@ OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, { CLASS_NAME: "OpenLayers.Renderer.Elements" }); - -/** - * Constant: OpenLayers.Renderer.symbol - * Coordinate arrays for well known (named) symbols. - */ -OpenLayers.Renderer.symbol = { - "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301, - 303,215, 231,161, 321,161, 350,75], - "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4, - 4,0], - "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0], - "square": [0,0, 0,1, 1,1, 1,0, 0,0], - "triangle": [0,10, 10,10, 5,0, 0,10] -}; diff --git a/lib/OpenLayers/Spherical.js b/lib/OpenLayers/Spherical.js index 4a8956a5b7..b3957d4910 100644 --- a/lib/OpenLayers/Spherical.js +++ b/lib/OpenLayers/Spherical.js @@ -3,6 +3,10 @@ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ +/** + * @requires OpenLayers/SingleFile.js + */ + /** * Namespace: Spherical * The OpenLayers.Spherical namespace includes utility functions for diff --git a/lib/OpenLayers/Tile.js b/lib/OpenLayers/Tile.js index d1d11eef8f..56c49c6320 100644 --- a/lib/OpenLayers/Tile.js +++ b/lib/OpenLayers/Tile.js @@ -40,6 +40,8 @@ OpenLayers.Tile = OpenLayers.Class({ * to call (true) to actually draw the tile. * loadstart - Triggered when tile loading starts. * loadend - Triggered when tile loading ends. + * loaderror - Triggered before the loadend event (i.e. when the tile is + * still hidden) if the tile could not be loaded. * reload - Triggered when an already loading tile is reloaded. * unload - Triggered before a tile is unloaded. */ diff --git a/lib/OpenLayers/Tile/Image.js b/lib/OpenLayers/Tile/Image.js index 5332c1581d..778ececc92 100644 --- a/lib/OpenLayers/Tile/Image.js +++ b/lib/OpenLayers/Tile/Image.js @@ -208,11 +208,12 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * code. */ positionTile: function() { - var style = this.getTile().style; + var style = this.getTile().style, + size = this.layer.getImageSize(this.bounds); style.left = this.position.x + "%"; style.top = this.position.y + "%"; - style.width = this.size.w + "%"; - style.height = this.size.h + "%"; + style.width = size.w + "%"; + style.height = size.h + "%"; }, /** @@ -256,11 +257,6 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { var top = this.layer.gutter / this.layer.tileSize.h * 100; style.left = -left + "%"; style.top = -top + "%"; - style.width = (2 * left + 100) + "%"; - style.height = (2 * top + 100) + "%"; - } else { - style.width = "100%"; - style.height = "100%"; } style.visibility = "hidden"; style.opacity = 0; @@ -429,6 +425,7 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { this.setImgSrc(this.layer.getURL(this.bounds)); } else { OpenLayers.Element.addClass(img, "olImageLoadError"); + this.events.triggerEvent("loaderror"); this.onImageLoad(); } } diff --git a/lib/OpenLayers/Tile/Image/IFrame.js b/lib/OpenLayers/Tile/Image/IFrame.js index eefa7bbe91..b29798cccc 100644 --- a/lib/OpenLayers/Tile/Image/IFrame.js +++ b/lib/OpenLayers/Tile/Image/IFrame.js @@ -65,7 +65,11 @@ OpenLayers.Tile.Image.IFrame = { // And if we had an iframe we also remove the event pane. if(fromIFrame) { + this.blankImageUrl = this._blankImageUrl; this.frame.removeChild(this.frame.firstChild); + } else { + this._blankImageUrl = this.blankImageUrl; + this.blankImageUrl = "about:blank"; } } } @@ -85,7 +89,7 @@ OpenLayers.Tile.Image.IFrame = { style.width = "100%"; style.height = "100%"; style.zIndex = 1; - style.backgroundImage = "url(" + this.blankImageUrl + ")"; + style.backgroundImage = "url(" + this._blankImageUrl + ")"; this.frame.appendChild(eventPane); } @@ -133,7 +137,7 @@ OpenLayers.Tile.Image.IFrame = { return OpenLayers.Tile.Image.prototype.getImage.apply(this, arguments); } }, - + /** * Method: createRequestForm * Create the html
element with width, height, bbox and all diff --git a/lib/OpenLayers/Tween.js b/lib/OpenLayers/Tween.js index e30d5d607b..85718c0953 100644 --- a/lib/OpenLayers/Tween.js +++ b/lib/OpenLayers/Tween.js @@ -1,8 +1,9 @@ /* Copyright (c) 2006-2012 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the Clear BSD license. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the - * full text of the license. - * + * full text of the license. */ + +/** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Animation.js */ diff --git a/lib/OpenLayers/Util.js b/lib/OpenLayers/Util.js index 5ca8c35201..880e3435ab 100644 --- a/lib/OpenLayers/Util.js +++ b/lib/OpenLayers/Util.js @@ -560,8 +560,10 @@ OpenLayers.Util.urlAppend = function(url, paramStr) { }; /** - * Property: ImgPath - * {String} Default is ''. + * APIProperty: ImgPath + * {String} Set this to the path where control images are stored. + * If set to '' OpenLayers will use script location + "img/" + * Default is ''. */ OpenLayers.ImgPath = ''; diff --git a/notes/2.12.md b/notes/2.12.md index 3aa2db8eda..b188b80e36 100644 --- a/notes/2.12.md +++ b/notes/2.12.md @@ -84,8 +84,39 @@ The `OpenLayers.Tile.Image` class now has a method to get a canvas context for p tileOptions: {crossOriginKeyword: null} +Both `OpenLayers.Layer.OSM` and `OpenLayers.Layer.Bing` do not have defaults for `maxExtent`, `maxResolutions` and `units` any more. This may break maps that are configured with a `maxResolution` of `156543.0339`, which was used in examples before 2.11, but is incorrect. The correct value is `156543.03390625`, but it is no longer necessary to specify a maxResolution, maxExtent and units if the correct resolution is set. See "Projection and Spherical Mercator" below. + ## Projection & SphericalMercator +When working with Web Mercator layers (e.g. Google, Bing, OSM), it was previously necessary to configure the map or the base layer with the correct `projection`, `maxExtent`, `maxResolutions` and `units`. Now OpenLayers has defaults for WGS84 and Web Mercator in `OpenLayers.Projection.defaults`, so it is enough to provide the `projection`. + +Old: + + new OpenLayers.Map({ + div: "map", + projection: "EPSG:900913", + maxResolution: 156543.03390625, + maxExtent: new OpenLayers.Bounds(-20037508.34, -20037508.34, 20037508.34, 20037508.34), + units: "m", + layers: [ + new OpenLayers.Layer.Google("Google Streets"), + new OpenLayers.Layer.OSM(null, null, {isBaseLayer: false, opacity: 0.7}) + ], + zoom: 1 + }); + +New: + + new OpenLayers.Map({ + div: "map", + projection: "EPSG:900913", + layers: [ + new OpenLayers.Layer.Google("Google Streets"), + new OpenLayers.Layer.OSM(null, null, {isBaseLayer: false, opacity: 0.7}) + ], + zoom: 1 + }); + In previous releases, coordinate transforms between EPSG:4326 and EPSG:900913 were defined in the SphericalMercator.js script. In 2.12, these default transforms are included in the Projection.js script. The Projection.js script is included as a dependency in builds with any layer types, so no special build configuration is necessary to get the web mercator transforms. If you were previously using the `OpenLayers.Layer.SphericalMercator.forwardMercator` or `inverseMercator` methods, you may have to explicitly include the SphericalMercator.js script in your build. The Google layer is the only layer that depends on the SphericalMercator mixin. If you are not using the Google layer but want to use the SphericalMercator methods listed above, you have to explicitly include the SphericalMercator.js script in your build. @@ -98,6 +129,16 @@ If you were previously using the `OpenLayers.Layer.SphericalMercator.forwardMerc The internal `OpenLayers.Layer.getURLasync` function now take a bound, a callback and a scope. The function no longer needs update the passed property but simply to return to url. +## Changes when base layer configured with wrapDateLine: true + +Vector editing across the date line works reliably now. To make this work, OpenLayers won't zoom out to resolutions where more than one world is visible any more. For maps that have base layers with wrapDateLine set to false, no zoom restrictions apply. + +## OpenLayers.Util.onImageLoadError no longer exists + +To replace a tile that couldn't be loaded with a static image, create a css selector for the `.olImageLoadError` class (e.g. a `background-image`). + +For more complex tile loading error handling, register a listener to the layer's `tileerror` event. + ## Deprecated Components A number of properties, methods, and constructors have been marked as deprecated for multiple releases in the 2.x series. For the 2.12 release this deprecated functionality has been moved to a separate deprecated.js file. If you use any of the constructors or methods below, you will have to explicitly include the deprecated.js file in your build (or add it in a separate ` -
+
diff --git a/tests/Control/Permalink.html b/tests/Control/Permalink.html index 4b07d3e2bb..b398adf148 100644 --- a/tests/Control/Permalink.html +++ b/tests/Control/Permalink.html @@ -11,6 +11,7 @@ t.eq(control.displayClass, "olControlPermalink", "displayClass is correct"); t.eq(control.base, document.location.href, "base is correct"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink('permalink', 'test.html'); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); @@ -18,6 +19,7 @@ t.eq(control.base, 'test.html', "base is correct"); t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink('permalink'); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); @@ -25,6 +27,7 @@ t.eq(control.base, document.location.href, "base is correct"); t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink(OpenLayers.Util.getElement('permalink')); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); @@ -32,6 +35,7 @@ t.eq(control.base, document.location.href, "base is correct"); t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink({anchor: true}); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); @@ -39,18 +43,21 @@ t.eq(control.base, document.location.href, "base is correct"); t.ok(control.element == null, "element is null"); t.ok(control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink({anchor: false}); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); t.eq(control.displayClass, "olControlPermalink", "displayClass is correct"); t.eq(control.base, document.location.href, "base is correct"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink({}); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); t.eq(control.displayClass, "olControlPermalink", "displayClass is correct"); t.eq(control.base, document.location.href, "base is correct"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink({element: 'permalink', base: 'test.html'}); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); @@ -58,6 +65,7 @@ t.eq(control.base, 'test.html', "base is correct"); t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object"); t.ok(!control.anchor, "anchor is correct"); + control.destroy(); control = new OpenLayers.Control.Permalink({element: 'permalink', base: 'test.html', anchor: true}); t.ok(control instanceof OpenLayers.Control.Permalink, "new OpenLayers.Control returns object"); @@ -65,6 +73,7 @@ t.eq(control.base, 'test.html', "base is correct"); t.ok(OpenLayers.Util.isElement(control.element), "element is a dom object"); t.ok(control.anchor, "anchor is correct"); + control.destroy(); } function test_Control_Permalink_uncentered (t) { t.plan( 1 ); @@ -74,12 +83,14 @@ map.addControl(control); map.events.triggerEvent("changelayer", {}); t.ok(true, "permalink didn't bomb out."); + map.destroy(); } function test_Control_Permalink_initwithelem (t) { t.plan( 1 ); control = new OpenLayers.Control.Permalink(OpenLayers.Util.getElement('permalink')); t.ok(true, "If the above line doesn't throw an error, we're safe."); + control.destroy(); } function test_Control_Permalink_updateLinks (t) { t.plan( 3 ); @@ -100,6 +111,7 @@ map.layers[1].setVisibility(false); t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getElement('permalink').href, location+"?zoom=2&lat=0&lon=1.75781&layers=BF"), 'setVisibility sets permalink'); + map.destroy(); } function test_Control_Permalink_updateLinksBase (t) { t.plan( 2 ); @@ -114,6 +126,7 @@ map.pan(5, 0, {animate:false}); OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B'; t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base"); + map.destroy(); } function test_Control_Permalink_noElement (t) { t.plan( 2 ); @@ -122,6 +135,7 @@ map = new OpenLayers.Map('map'); map.addControl(control); t.eq(map.controls[4].div.firstChild.nodeName, "A", "Permalink control creates div with 'a' inside." ); + map.destroy(); } function test_Control_Permalink_base_with_query (t) { t.plan( 3 ); @@ -147,6 +161,7 @@ map.pan(5, 0, {animate:false}); map.pan(-5, 0, {animate:false}); t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with base and querystring ending with '?'"); + map.destroy(); } @@ -163,6 +178,7 @@ map.pan(5, 0, {animate:false}); OpenLayers.Util.getElement('edit_permalink').href = './edit.html?zoom=2&lat=0&lon=1.75781&layers=B'; t.eq(OpenLayers.Util.getElement('permalink').href, OpenLayers.Util.getElement('edit_permalink').href, "Panning sets permalink with existing zoom in base"); + map.destroy(); } function test_Control_Permalink_customized(t) { @@ -189,6 +205,7 @@ 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."); + map.destroy(); } function test_Control_Permalink_createParams(t) { @@ -300,6 +317,7 @@ map.layers[1].setVisibility(false); t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getParameterString(control.createParams()), "zoom=2&lat=0&lon=1.75781&layers=BF"), 'setVisibility sets permalink'); + map.destroy(); } function test_Control_Permalink_AnchorBaseElement (t) { @@ -320,7 +338,80 @@ map.layers[1].setVisibility(false); t.ok(OpenLayers.Util.isEquivalentUrl(OpenLayers.Util.getElement('permalink').href, location+"#zoom=2&lat=0&lon=1.75781&layers=BF"), 'setVisibility sets permalink'); + map.destroy(); } + + function test_center_from_map(t) { + t.plan(7); + + var previous = window.location.hash; + window.location.hash = ""; + + var err; + try { + var map = new OpenLayers.Map({ + layers: [new OpenLayers.Layer(null, {isBaseLayer: true})], + controls: [ + new OpenLayers.Control.Permalink({anchor: true}) + ], + center: [1, 2], + zoom: 3 + }); + } catch (e) { + err = e; + } + if (err) { + t.fail("Map construction failure: " + err.message); + } else { + t.ok(true, "Map construction works"); + } + + // confirm that map center is correctly set + var center = map.getCenter(); + t.eq(center.lon, 1, "map x"); + t.eq(center.lat, 2, "map y") + t.eq(map.getZoom(), 3, "map z"); + + // confirm that location from map options has been added to url + var params = OpenLayers.Util.getParameters(window.location.hash.replace("#", "?")); + t.eq(params.lon, "1", "url x"); + t.eq(params.lat, "2", "url y"); + t.eq(params.zoom, "3", "url z"); + + map.destroy(); + window.location.hash = previous; + } + + function test_center_from_url(t) { + t.plan(6); + + // In cases where the location is specified in the URL and given in + // the map options, we respect the location in the URL. + var previous = window.location.hash; + window.location.hash = "#zoom=6&lat=5&lon=4&layers=B" + + var map = new OpenLayers.Map({ + layers: [new OpenLayers.Layer(null, {isBaseLayer: true})], + controls: [new OpenLayers.Control.Permalink({anchor: true})], + center: [0, 0], + zoom: 0 + }); + + // confirm that map center is correctly set + var center = map.getCenter(); + t.eq(center.lon, 4, "map x"); + t.eq(center.lat, 5, "map y") + t.eq(map.getZoom(), 6, "map z"); + + var params = OpenLayers.Util.getParameters(window.location.hash.replace("#", "?")); + t.eq(params.lon, "4", "x set"); + t.eq(params.lat, "5", "y set"); + t.eq(params.zoom, "6", "z set"); + + map.destroy(); + window.location.hash = previous; + } + diff --git a/tests/Control/WMSGetFeatureInfo.html b/tests/Control/WMSGetFeatureInfo.html index 5e7801b036..b5e6d5db80 100644 --- a/tests/Control/WMSGetFeatureInfo.html +++ b/tests/Control/WMSGetFeatureInfo.html @@ -475,6 +475,35 @@ } + function test_exceptions(t) { + t.plan(1); + var map = new OpenLayers.Map("map", { + getExtent: function() {return(new OpenLayers.Bounds(-180,-90,180,90));} + } + ); + + var a = new OpenLayers.Layer.WMS("dummy","http://myhost/wms", { + layers: "x", + exceptions: "text/xml" + }); + + map.addLayer(a); + + var click = new OpenLayers.Control.WMSGetFeatureInfo({ + }); + + map.addControl(click); + + var _request = OpenLayers.Request.GET; + OpenLayers.Request.GET = function(options) { + t.eq(options.params["EXCEPTIONS"], "text/xml", "Exceptions parameter taken from the WMS layer if provided"); + }; + click.activate(); + click.getInfoForClick({xy: {x: 50, y: 50}}); + OpenLayers.Request.GET = _request; + map.destroy(); + } + function test_drillDown(t) { t.plan(6); var map = new OpenLayers.Map("map", { diff --git a/tests/Control/Zoom.html b/tests/Control/Zoom.html new file mode 100644 index 0000000000..c27161dab8 --- /dev/null +++ b/tests/Control/Zoom.html @@ -0,0 +1,81 @@ + + + + + + + +
+
in
out + + diff --git a/tests/Events/buttonclick.html b/tests/Events/buttonclick.html index 9aff9b89b8..35ebb81f8a 100644 --- a/tests/Events/buttonclick.html +++ b/tests/Events/buttonclick.html @@ -27,9 +27,44 @@ buttonClick.destroy(); events.destroy(); } + + function test_getPressedButton(t) { + t.plan(4); + + // set up + + events = new OpenLayers.Events({}, element); + buttonClick = new OpenLayers.Events.buttonclick(events); + + var button = document.createElement('button'), + span1 = document.createElement('span'), + span2 = document.createElement('span'), + span3 = document.createElement('span'); + button.className = 'olButton'; + button.appendChild(span1); + span1.appendChild(span2); + span2.appendChild(span3); + + t.ok(buttonClick.getPressedButton(button) === button, + 'getPressedButton returns button when element is button'); + t.ok(buttonClick.getPressedButton(span1) === button, + 'getPressedButton returns button when element is button descendant level 1'); + t.ok(buttonClick.getPressedButton(span2) === button, + 'getPressedButton returns button when element is button descendant level 2'); + t.eq(buttonClick.getPressedButton(span3), undefined, + 'getPressedButton returns undefined when element is button descendant level 3'); + + // test + + + // tear down + + buttonClick.destroy(); + events.destroy(); + } function test_ButtonClick_buttonClick(t) { - t.plan(23); + t.plan(27); events = new OpenLayers.Events({}, element); events.on({ "buttonclick": logEvent, @@ -38,7 +73,8 @@ "click": logEvent, "dblclick": logEvent, "touchstart": logEvent, - "touchend": logEvent + "touchend": logEvent, + "keydown": logEvent }); buttonClick = events.extensions["buttonclick"]; @@ -111,12 +147,26 @@ t.eq(log[1].type, "buttonclick", "buttonclick for 2nd click IE"); // rightclick - log = [] + log = []; trigger({type: "mousedown", button: 2}); trigger({type: "mouseup", button: 2}); t.eq(log.length, 2, "two events fired for rightclick"); t.eq(log[0].type, "mousedown", "mousedown from rightclick goes through"); t.eq(log[1].type, "mouseup", "mouseup from rightclick goes through"); + + // keydown RETURN + log = []; + trigger({type: "keydown", keyCode: OpenLayers.Event.KEY_RETURN}); + trigger({type: "click"}); + t.eq(log.length, 1, "one event fired for RETURN keydown"); + t.eq(log[0].type, "buttonclick", "buttonclick for RETURN keydown"); + + // keydown SPACE + log = []; + trigger({type: "keydown", keyCode: OpenLayers.Event.KEY_SPACE}); + trigger({type: "click"}); + t.eq(log.length, 1, "one event fired for SPACE keydown"); + t.eq(log[0].type, "buttonclick", "buttonclick for SPACE keydown"); } diff --git a/tests/Format/KML.html b/tests/Format/KML.html index 42b6fe6e0d..a7dfd976c2 100644 --- a/tests/Format/KML.html +++ b/tests/Format/KML.html @@ -198,21 +198,35 @@ t.ok(style.t, "getStyle returns copy of style rather than reference"); } function test_Format_KML_extendedData(t) { - t.plan(2); + t.plan(6); var f = new OpenLayers.Format.KML(); var features = f.read(OpenLayers.Util.getElement("kml_extendeddata").value); - t.eq(features[0].attributes.all_bridges.value, "3030", "read value from extendeddata correctly."); - t.eq(features[0].attributes.all_bridges.displayName, "all bridges", "read displayName from extendeddata correctly."); + t.eq(features[0].attributes.holeYardage.value, "234", "read value from extendeddata correctly."); + t.eq(features[0].attributes.holeYardage.displayName, "The yardage is ", "read displayName from extendeddata correctly."); + t.eq(f.read(f.write(features[0]))[0].attributes.holeYardage.value, features[0].attributes.holeYardage.value, "attribute value written correctly"); + t.eq(f.read(f.write(features[0]))[0].attributes.holeYardage.displayName, features[0].attributes.holeYardage.displayName, "attribute displayName written correctly"); + f.kvpAttributes = true; + features = f.read(OpenLayers.Util.getElement("kml_extendeddata").value); + t.eq(features[0].attributes.holeYardage, "234", "read kvp value from extendeddata correctly."); + t.eq(f.read(f.write(features[0]))[0].attributes.holeYardage, features[0].attributes.holeYardage, "kvp attribute value written correctly"); } function test_Format_KML_extendedData_SchemaData(t) { - t.plan(4); + t.plan(10); var f = new OpenLayers.Format.KML(); var features = f.read(OpenLayers.Util.getElement("kml_extendeddata2").value); t.eq(features[0].attributes.TrailHeadName.value, "Pi in the sky", "read value from extendeddata (schema data) correctly."); t.eq(features[0].attributes.TrailHeadName.displayName, "TrailHeadName", "read displayName from extendeddata correctly"); t.eq(features[0].attributes.ElevationGain.value, "10", "read value from extendeddata (schema data) correctly."); t.eq(features[0].attributes.ElevationGain.displayName, "ElevationGain", "read displayName from extendeddata correctly"); + t.eq(f.read(f.write(features[0]))[0].attributes.TrailHeadName.value, features[0].attributes.TrailHeadName.value, "attribute value from extendeddata (schema data) written correctly"); + t.eq(f.read(f.write(features[0]))[0].attributes.ElevationGain.value, features[0].attributes.ElevationGain.value, "attribute value from extendeddata (schema data) written correctly"); + f.kvpAttributes = true; + features = f.read(OpenLayers.Util.getElement("kml_extendeddata2").value); + t.eq(features[0].attributes.TrailHeadName, "Pi in the sky", "read kvp value from extendeddata (schema data) correctly."); + t.eq(features[0].attributes.ElevationGain, "10", "read kvp value from extendeddata (schema data) correctly."); + t.eq(f.read(f.write(features[0]))[0].attributes.TrailHeadName, features[0].attributes.TrailHeadName, "kvp attribute value from extendeddata (schema data) written correctly"); + t.eq(f.read(f.write(features[0]))[0].attributes.ElevationGain, features[0].attributes.ElevationGain, "kvp attribute value from extendeddata (schema data) written correctly"); } function test_Format_KML_placemarkName(t) { @@ -287,49 +301,61 @@