Dart Documentationdartkart.mapMapViewport

MapViewport class

A MapViewport provides a view on a rectangular area of a map plane in a stack of map planes on different zoom levels.

It also provides the functionality to change between map planes (zoom in and zoom out) and to move the viewport around on the current map plane (pan left, rigth, up, or down).

A MapViewport manages a stack of map Layers.

A MapViewport is a PropertyObservable. It emits propery change events for zoom - emitted if the zoom level is changed center - emitted if the center of the map viewport is changed

class MapViewport extends Object with PropertyObservable{

 DivElement _root;

 /// the root DOM element of the map viewport
 Element get root => _root;

 //TODO: make configurable.
 final ProjectedCRS _crs = new EPSG3857();
 ProjectedCRS get crs => _crs;

 /**
  * Creates a map viewport.
  *
  * [container] is either an [Element] or a string consisting of
  * a CSS selector.
  */
 MapViewport(container) {
   _require(container != null,"container must not be null");
   if (container is String) {
     container = query(container);
     _require(container != null, "didn't find container with id '$container'");
   } else if (container is Element) {
     // OK
   } else {
     _require(false,"expected Element or String, got $container");
   }
   _root = new DivElement()
     ..classes.add("dartkart-map-viewport");
   
   container.children
     ..clear()
     ..add(_root);
   attachEventListeners();
   controlsPane = new ControlsPane();
 }

 void attachEventListeners() {
   //TODO: remind subscription; solve detach
   _root.onMouseWheel.listen(_onMouseWheel);
   new _DragController(this);
   new _DoubleClickController(this);
 }

 /// Transforms projected coordinates [p] to coordinates in the current map
 /// zoom plane
 Point2D mapToZoomPlane(Point2D p) {
   var zp = zoomPlaneSize;
   var w = _crs.projectedBounds.width;
   var h = _crs.projectedBounds.height;
   return p.flipY()
    .translate(dx: w/2, dy: h/2)
    .scale(sx: zp.width/w, sy: zp.height/h)
    .toInt();
 }

 /// Transforms coordinates [p] in the current map zoom plane to
 /// projected coordinates
 Point2D zoomPlaneToMap(Point2D p) {
   var zp = zoomPlaneSize;
   var w = _crs.projectedBounds.width;
   var h = _crs.projectedBounds.height;
   return p.scale(sx: w/zp.width, sy: h/zp.height)
       .translate(dx:-w/2, dy:-h/2)
       .flipY();
 }

 /// Transforms coordinates in the current map zoom plane to viewport
 /// coordinates
 Point2D zoomPlaneToViewport(Point2D p) {
  var centerOnZoomPlane = mapToZoomPlane(earthToMap(center));
  return (p - centerOnZoomPlane).toInt() + (viewportSize / 2);
 }

 /// viewport coordinates to coordinates in the current map zoom plane
 Point2D viewportToZoomPlane(Point2D p) {
   var centerOnZoomPlane = mapToZoomPlane(earthToMap(center));
   var delta = p - (viewportSize / 2);
   return (centerOnZoomPlane + delta).toInt();
 }

 /// Transforms geographic coordinates [ll] to projected coordinates
 Point2D earthToMap(LatLon ll) => _crs.project(ll);

 /// Transforms projected coordinates [p] to geographic coordinates
 LatLon mapToEarth(Point2D p) => _crs.unproject(p);

 /// the viewport size
 Dimension get viewportSize =>
     new Dimension(_root.client.width, _root.client.height);

 /// the size of the current map zoom plane
 Dimension get zoomPlaneSize {
   var dim = (1 << zoom) * 256;
   return new Dimension(dim, dim);
 }

 /// the top-left point in "page coordinates"
 Point2D get topLeftInPage {
   offset(Element e) {
     var p = new Point2D(e.offset.left, e.offset.top);
     return e.parent == null ? p : p + offset(e.parent);
   }
   return offset(_root);
 }

 /**
  * The bounding box of the viewport in which we are currently rending
  * part of the map.
  *
  * The screen bounding box depends on the current zoom level ant the
  * current map center. In most cases, in partiuclar on zoom levels > 2,
  * it is equal to the extend of the map viewport. In lower zoom levels,
  * where the zoom plane is smaller than the map viewport, or if the
  * center is moved very far east, west, north, or south, it only covers
  * part of the viewport.
  *
  */
 Bounds get screenBoundingBox {
   var vpSize = viewportSize;
   var vpCenter = (viewportSize / 2).toInt();
   var zpSize = zoomPlaneSize;
   var zpCenter = mapToZoomPlane(earthToMap(center));
   var zp = zoomPlaneSize;
   var x = math.max(0, vpCenter.x - zpCenter.x);
   var y = math.max(0, vpCenter.y - zpCenter.y);
   var width = math.min(vpSize.x, vpCenter.x  + (zpSize.x - zpCenter.x));
   var height = math.min(vpSize.y, vpCenter.y  + (zpSize.y - zpCenter.y));
   return new Bounds([x,y], [x+width, y+height]);
 }

 /**
  * Transforms page coordinates to viewport coordinates.
  *
  * [v] is either
  *   * a [Point2D]
  *   * a [MouseEvent] - uses the coordinates (pageX, pageY)
  *
  *  The result are viewport coordinates for the map viewport where
  *    * (0,0) is the upper left corner of the map viewport
  *    * x runs to the right
  *    * y runs down
  */
 Point2D pageToViewport(v) {
   if (v is MouseEvent) {
     v = new Point2D(v.page.x, v.page.y);
   } else if (v is Point2D) {} // do nothing
   else throw new ArgumentError("expected MouseEvent or Point2D, got $v");
   return v - topLeftInPage;
 }

 /**
  * Renders the map.
  */
 void render() {
   _layers.forEach((l)=> l.render());
   _controlsPane.layout();
 }

 /* ----------------------- layer handling -------------------------- */
 final List<Layer> _layers = [];

 /// update the z-indexes of the layer. Reflects the ordering in
 /// the layer stack. The layer with the highest index is renderer
 /// on top, the layer with index 0 is rendered at the bottom.
 _updateLayerZIndex() {
   var reversed = _layers.reversed.toList();
   for (int i=0; i<layers.length; i++) {
     reversed[i].container.style.zIndex = (i * -100).toString();
   }
 }

 /**
  * Adds a [layer] to the map.
  *
  * [layer] is appended to the list of layers of this map. It therefore
  * has the highest layer index and becomes rendered on top of the layer
  * stack of this map.
  *
  * Throws [ArgumentError] if [layer] is null. [layer] is ignored if it
  * is already attached to this map.
  *
  * ##Example
  *    var source= "http://a.tile.openstreetmap.org/{z}/{x}/{y}.png";
  *    map.addLayer(new OsmLayer(tileSource: source));
  */
 void addLayer(Layer layer) {
   _require(layer != null, "layer must not be null");
   if (hasLayer(layer)) return;
   _layers.add(layer);
   _root.children.add(layer.container);
   layer.attach(this);
   _updateLayerZIndex();
   render();
   _notifyLayerEvent(layer,LayerEvent.ADDED);
 }

 /**
  * Removes [layer] from the stack of layers of this map.
  *
  * Ignores [layer] if it is null or if it isn't attached to this map.
  */
 void removeLayer(Layer layer) {
   if (layer == null) return;
   if (!hasLayer(layer)) return;
   layer.detach();
   _root.children.remove(layer.container);
   _layers.remove(layer);
   _updateLayerZIndex();
   render();
   _notifyLayerEvent(layer,LayerEvent.REMOVED);
 }

 /// true, if [layer] is part of the layer stack of this map
 bool hasLayer(Layer layer) => _layers.contains(layer);

 /// an unmodifiable list of layers of this map. Empty, if
 /// no layes are defined.
 List<Layer> get layers => new UnmodifiableListView(_layers);

 /**
  * Moves the [layer] to the top.
  */
 void moveToTop(Layer layer) {
   if (!hasLayer(layer)) return;
   _layers.remove(layer);
   _layers.add(layer);
   _updateLayerZIndex();
   _notifyLayerEvent(layer,LayerEvent.MOVED);
 }

 /**
  * Moves the [layer] to the bottom.
  */
 void moveToBottom(Layer layer) {
   if (!hasLayer(layer)) return;
   _layers
     ..remove(layer)
     ..insert(0, layer);
   _updateLayerZIndex();
   _notifyLayerEvent(layer,LayerEvent.MOVED);
 }

 /**
  * Moves the [layer] to the position [index] in the
  * layer stack.
  */
 void moveTo(Layer layer, int index) {
   if (!hasLayer(layer)) return;
   index = math.max(index, 0);
   index = math.min(index, _layers.length);
   _layers
     ..remove(layer)
     ..insert(index,layer);
   _updateLayerZIndex();
   _notifyLayerEvent(layer,LayerEvent.MOVED);
 }

 final StreamController<LayerEvent> _layerEventsController =
     new StreamController<LayerEvent>();
 Stream<LayerEvent> _layerEventsStream;
 
 _notifyLayerEvent(layer, type) {
   if (!_layerEventsController.hasListener) return;
   if (_layerEventsController.isPaused) return;
   var event = new LayerEvent(this, layer, type);
   _layerEventsController.sink.add(event);
 }

 /**
  * Stream of layer change events.
  *
  * ## Example
  *
  *     map.onLayersChanged
  *       .where((LayerEvent e) => e.type == LayerEvent.ADDED))
  *       .listen((LayerEvent e) {
  *           print("layer added - num layers: ${map.layers.length}");
  *       });
  */
 Stream<LayerEvent> get onLayersChanged {
   if (_layerEventsStream == null) {
     _layerEventsStream = _layerEventsController.stream.asBroadcastStream();
   }
   return _layerEventsStream;
 }

 /* ----------------------- zooming       --------------------------- */
 int _zoom = 0;

 /// the current zoom level
 int get zoom => _zoom;

 /**
  * Set the zoom level [zoom].
  *
  * [zoom] >= 0 expected, otherwise throws an [ArgumentError].
  */
 void set zoom(int value) {
   _require(value >= 0, "zoom >= 0 expected, got $value");
   if (value == _zoom) return;
   _zoom = value;
   render();
   notify("zoom", _zoom, value);
 }

 /**
  * Zoom in by [delta] zoom levels.
  *
  * Throws [ArgumentError] if [delta] < 0. Fires a zoom change event.
  */
 void zoomIn([int delta=1]) {
   _require(delta >= 0, "delta >= 0 expected, got $delta");
   if (delta == 0) return;

   //TODO: check for max zoom level
   var oldZoom = _zoom;
   _zoom+= delta;
   render();
   notify("zoom", oldZoom, _zoom);
 }

 /**
  * Zoom out by [delta] zoom levels.
  *
  * Throws [ArgumentError] if [delta] < 0. Fires a zoom change event.
  */
 void zoomOut([int delta=1]) {
   if (delta < 0) throw new ArgumentError("delta >= 0 expected, got $delta");
   if (delta == 0) return;
   var oldZoom = _zoom;
   _zoom = math.max(0, _zoom - delta);
   render();
   notify("zoom", oldZoom, _zoom);
 }

 /* ----------------------- event handlers --------------------------- */
 _onMouseWheel(WheelEvent evt) {
   var doZoom = evt.deltaY < 0 ? zoomIn : zoomOut;
   doZoom();
 }

 /* --------------------- map center ---------------------------------- */
 LatLon _center = new LatLon.origin();

 /// the current map center in geographic coordinates
 LatLon get center => _center;

 /**
  * Sets the current map [center].
  *
  * [center] must not be null. Broadcasts a [PropertyChangeEvent] if
  * the center is changed, see [onCenterChanged].
  */
 void set center(LatLon value) {
   _require(value != null, "center must not be null");
   if (_center == value) return;
   var old = _center;
   _center = value;    
   render();
   notify("center", old, _center);
 }

 /* --------------------- panning ---------------------------------- */
 void pan(delta, {bool animate: false}) {
   if (animate) {
     new PanBehaviour(this).animate(new Point2D.from(delta));
   } else {
     delta = new Point2D.from(delta);
     var p = mapToZoomPlane(earthToMap(center));
     p = p + delta;
     if (p.x <= 0 || p.y <= 0) return;
     var size = zoomPlaneSize;
     if (p.x >= size.width || p.y >= size.height) return;
     var c = zoomPlaneToMap(p);
     if (!crs.projectedBounds.contains(c)) return;
     center = mapToEarth(c);
   }
 }

 /// Pans the viewport num [pixels] to the north.
 /// Animates panning if [animate] is true.
 void panNorth({int pixels:100, bool animate:false}) =>
     pan([0,-pixels], animate: animate);

 /// Pans the viewport num [pixels] to the south.
 /// Animates panning if [animate] is true.
 void panSouth({int pixels:100, bool animate:false}) =>
     pan([0,pixels], animate: animate);

 /// Pans the viewport num [pixels] to the west
 /// Animates panning if [animate] is true.
 void panWest({int pixels:100, bool animate:false}) =>
     pan([-pixels, 0], animate: animate);

 /// Pans the viewport num [pixels] to the east
 /// Animates panning if [animate] is true.
 void panEast({int pixels:100, bool animate:false}) =>
     pan([pixels, 0],animate: animate);

 /* ----------------------- controls pane ------------------------ */
 ControlsPane _controlsPane;

 /// the pane with the interactive map controls
 ControlsPane get controlsPane => _controlsPane;

 /// sets the [pane] for the interactive map controls
 void set controlsPane(ControlsPane pane) {
   if (pane == _controlsPane) return; // don't add twice
   if (_controlsPane != null) {
     _controlsPane.detach();
     _root.children.remove(_controlsPane.root);
   }
   _controlsPane = pane;
   if (_controlsPane != null) {
     _controlsPane
       ..attach(this)
       // render the controls pane on top of the map layers.
       // The z-index for the top most layer is 0.
       ..root.style.zIndex = "100";
     _root.children.add(_controlsPane.root);
   }
 }
}

Extends

Object_PropertyObservable > MapViewport

Constructors

new MapViewport(container) #

Creates a map viewport.

container is either an Element or a string consisting of a CSS selector.

MapViewport(container) {
 _require(container != null,"container must not be null");
 if (container is String) {
   container = query(container);
   _require(container != null, "didn't find container with id '$container'");
 } else if (container is Element) {
   // OK
 } else {
   _require(false,"expected Element or String, got $container");
 }
 _root = new DivElement()
   ..classes.add("dartkart-map-viewport");
 
 container.children
   ..clear()
   ..add(_root);
 attachEventListeners();
 controlsPane = new ControlsPane();
}

Properties

LatLon get center #

the current map center in geographic coordinates

LatLon get center => _center;

void set center(LatLon value) #

Sets the current map center.

center must not be null. Broadcasts a PropertyChangeEvent if the center is changed, see onCenterChanged.

void set center(LatLon value) {
 _require(value != null, "center must not be null");
 if (_center == value) return;
 var old = _center;
 _center = value;    
 render();
 notify("center", old, _center);
}

ControlsPane get controlsPane #

the pane with the interactive map controls

ControlsPane get controlsPane => _controlsPane;

void set controlsPane(ControlsPane pane) #

sets the pane for the interactive map controls

void set controlsPane(ControlsPane pane) {
 if (pane == _controlsPane) return; // don't add twice
 if (_controlsPane != null) {
   _controlsPane.detach();
   _root.children.remove(_controlsPane.root);
 }
 _controlsPane = pane;
 if (_controlsPane != null) {
   _controlsPane
     ..attach(this)
     // render the controls pane on top of the map layers.
     // The z-index for the top most layer is 0.
     ..root.style.zIndex = "100";
   _root.children.add(_controlsPane.root);
 }
}

final ProjectedCRS crs #

ProjectedCRS get crs => _crs;

final List<Layer> layers #

an unmodifiable list of layers of this map. Empty, if no layes are defined.

List<Layer> get layers => new UnmodifiableListView(_layers);

final Stream<LayerEvent> onLayersChanged #

Stream of layer change events.

Example

map.onLayersChanged
  .where((LayerEvent e) => e.type == LayerEvent.ADDED))
  .listen((LayerEvent e) {
      print("layer added - num layers: ${map.layers.length}");
  });
Stream<LayerEvent> get onLayersChanged {
 if (_layerEventsStream == null) {
   _layerEventsStream = _layerEventsController.stream.asBroadcastStream();
 }
 return _layerEventsStream;
}

final Stream<PropertyChangeEvent> onPropertyChanged #

the stream of property change events

Example

 // an observable with a mixed in PropertyObservable
 var observable = ...;
 // listen for property change events for the property
 // 'my_property'
 observable.onPropertyChanged
   .where((evt) => evt.name == "my_property")
   .listen((evt) => print("new value: ${evt.newValue}"));
Stream<PropertyChangeEvent> get onPropertyChanged {
 //TODO: fix this - if at least one listener is present,
 // change events are emitted, regardless of whether the
 // individual listeners are paused or not. Consequence:
 // lots of change events are possibly queued up in
 // paused listener streams. => need a custom implementation
 // of a multiplexing stream which disards events if
 // they are streamed to a disabled listener
 //
 if (_stream == null) {
   _stream = _controller.stream.asBroadcastStream();
 }
 return _stream;
}

final Element root #

the root DOM element of the map viewport

Element get root => _root;

final Bounds screenBoundingBox #

The bounding box of the viewport in which we are currently rending part of the map.

The screen bounding box depends on the current zoom level ant the current map center. In most cases, in partiuclar on zoom levels > 2, it is equal to the extend of the map viewport. In lower zoom levels, where the zoom plane is smaller than the map viewport, or if the center is moved very far east, west, north, or south, it only covers part of the viewport.

Bounds get screenBoundingBox {
 var vpSize = viewportSize;
 var vpCenter = (viewportSize / 2).toInt();
 var zpSize = zoomPlaneSize;
 var zpCenter = mapToZoomPlane(earthToMap(center));
 var zp = zoomPlaneSize;
 var x = math.max(0, vpCenter.x - zpCenter.x);
 var y = math.max(0, vpCenter.y - zpCenter.y);
 var width = math.min(vpSize.x, vpCenter.x  + (zpSize.x - zpCenter.x));
 var height = math.min(vpSize.y, vpCenter.y  + (zpSize.y - zpCenter.y));
 return new Bounds([x,y], [x+width, y+height]);
}

final Point2D topLeftInPage #

the top-left point in "page coordinates"

Point2D get topLeftInPage {
 offset(Element e) {
   var p = new Point2D(e.offset.left, e.offset.top);
   return e.parent == null ? p : p + offset(e.parent);
 }
 return offset(_root);
}

final Dimension viewportSize #

the viewport size

Dimension get viewportSize =>
   new Dimension(_root.client.width, _root.client.height);

int get zoom #

the current zoom level

int get zoom => _zoom;

void set zoom(int value) #

Set the zoom level zoom.

zoom >= 0 expected, otherwise throws an ArgumentError.

void set zoom(int value) {
 _require(value >= 0, "zoom >= 0 expected, got $value");
 if (value == _zoom) return;
 _zoom = value;
 render();
 notify("zoom", _zoom, value);
}

final Dimension zoomPlaneSize #

the size of the current map zoom plane

Dimension get zoomPlaneSize {
 var dim = (1 << zoom) * 256;
 return new Dimension(dim, dim);
}

Methods

void addLayer(Layer layer) #

Adds a layer to the map.

layer is appended to the list of layers of this map. It therefore has the highest layer index and becomes rendered on top of the layer stack of this map.

Throws ArgumentError if layer is null. layer is ignored if it is already attached to this map.

Example

var source= "http://a.tile.openstreetmap.org/{z}/{x}/{y}.png"; map.addLayer(new OsmLayer(tileSource: source));

void addLayer(Layer layer) {
 _require(layer != null, "layer must not be null");
 if (hasLayer(layer)) return;
 _layers.add(layer);
 _root.children.add(layer.container);
 layer.attach(this);
 _updateLayerZIndex();
 render();
 _notifyLayerEvent(layer,LayerEvent.ADDED);
}

void attachEventListeners() #

void attachEventListeners() {
 //TODO: remind subscription; solve detach
 _root.onMouseWheel.listen(_onMouseWheel);
 new _DragController(this);
 new _DoubleClickController(this);
}

Point2D earthToMap(LatLon ll) #

Transforms geographic coordinates ll to projected coordinates

Point2D earthToMap(LatLon ll) => _crs.project(ll);

bool hasLayer(Layer layer) #

true, if layer is part of the layer stack of this map

bool hasLayer(Layer layer) => _layers.contains(layer);

LatLon mapToEarth(Point2D p) #

Transforms projected coordinates p to geographic coordinates

LatLon mapToEarth(Point2D p) => _crs.unproject(p);

Point2D mapToZoomPlane(Point2D p) #

Transforms projected coordinates p to coordinates in the current map zoom plane

Point2D mapToZoomPlane(Point2D p) {
 var zp = zoomPlaneSize;
 var w = _crs.projectedBounds.width;
 var h = _crs.projectedBounds.height;
 return p.flipY()
  .translate(dx: w/2, dy: h/2)
  .scale(sx: zp.width/w, sy: zp.height/h)
  .toInt();
}

void moveTo(Layer layer, int index) #

Moves the layer to the position index in the layer stack.

void moveTo(Layer layer, int index) {
 if (!hasLayer(layer)) return;
 index = math.max(index, 0);
 index = math.min(index, _layers.length);
 _layers
   ..remove(layer)
   ..insert(index,layer);
 _updateLayerZIndex();
 _notifyLayerEvent(layer,LayerEvent.MOVED);
}

void moveToBottom(Layer layer) #

Moves the layer to the bottom.

void moveToBottom(Layer layer) {
 if (!hasLayer(layer)) return;
 _layers
   ..remove(layer)
   ..insert(0, layer);
 _updateLayerZIndex();
 _notifyLayerEvent(layer,LayerEvent.MOVED);
}

void moveToTop(Layer layer) #

Moves the layer to the top.

void moveToTop(Layer layer) {
 if (!hasLayer(layer)) return;
 _layers.remove(layer);
 _layers.add(layer);
 _updateLayerZIndex();
 _notifyLayerEvent(layer,LayerEvent.MOVED);
}

void notify(String property, oldValue, newValue) #

Notifies observers about an update of the property with name property in this object. oldValue was replaced by newValue.

Observers are only notified, provided newValue is different from oldValue and if there is at least one listener.

void notify(String property, oldValue, newValue) {
 if (oldValue == newValue) return;
 //TODO: fix me - see notes in onPropertyChanged
 if (!_controller.hasListener || _controller.isPaused) return;
 _controller.sink.add(
     new PropertyChangeEvent(this, property,oldValue,newValue)
 );
}

Point2D pageToViewport(v) #

Transforms page coordinates to viewport coordinates.

v is either * a Point2D * a MouseEvent - uses the coordinates (pageX, pageY)

The result are viewport coordinates for the map viewport where * (0,0) is the upper left corner of the map viewport * x runs to the right * y runs down

Point2D pageToViewport(v) {
 if (v is MouseEvent) {
   v = new Point2D(v.page.x, v.page.y);
 } else if (v is Point2D) {} // do nothing
 else throw new ArgumentError("expected MouseEvent or Point2D, got $v");
 return v - topLeftInPage;
}

void pan(delta, {bool animate: false}) #

void pan(delta, {bool animate: false}) {
 if (animate) {
   new PanBehaviour(this).animate(new Point2D.from(delta));
 } else {
   delta = new Point2D.from(delta);
   var p = mapToZoomPlane(earthToMap(center));
   p = p + delta;
   if (p.x <= 0 || p.y <= 0) return;
   var size = zoomPlaneSize;
   if (p.x >= size.width || p.y >= size.height) return;
   var c = zoomPlaneToMap(p);
   if (!crs.projectedBounds.contains(c)) return;
   center = mapToEarth(c);
 }
}

void panEast({int pixels: 100, bool animate: false}) #

Pans the viewport num pixels to the east Animates panning if animate is true.

void panEast({int pixels:100, bool animate:false}) =>
   pan([pixels, 0],animate: animate);

void panNorth({int pixels: 100, bool animate: false}) #

Pans the viewport num pixels to the north. Animates panning if animate is true.

void panNorth({int pixels:100, bool animate:false}) =>
   pan([0,-pixels], animate: animate);

void panSouth({int pixels: 100, bool animate: false}) #

Pans the viewport num pixels to the south. Animates panning if animate is true.

void panSouth({int pixels:100, bool animate:false}) =>
   pan([0,pixels], animate: animate);

void panWest({int pixels: 100, bool animate: false}) #

Pans the viewport num pixels to the west Animates panning if animate is true.

void panWest({int pixels:100, bool animate:false}) =>
   pan([-pixels, 0], animate: animate);

void removeLayer(Layer layer) #

Removes layer from the stack of layers of this map.

Ignores layer if it is null or if it isn't attached to this map.

void removeLayer(Layer layer) {
 if (layer == null) return;
 if (!hasLayer(layer)) return;
 layer.detach();
 _root.children.remove(layer.container);
 _layers.remove(layer);
 _updateLayerZIndex();
 render();
 _notifyLayerEvent(layer,LayerEvent.REMOVED);
}

void render() #

Renders the map.

void render() {
 _layers.forEach((l)=> l.render());
 _controlsPane.layout();
}

Point2D viewportToZoomPlane(Point2D p) #

viewport coordinates to coordinates in the current map zoom plane

Point2D viewportToZoomPlane(Point2D p) {
 var centerOnZoomPlane = mapToZoomPlane(earthToMap(center));
 var delta = p - (viewportSize / 2);
 return (centerOnZoomPlane + delta).toInt();
}

void zoomIn([int delta = 1]) #

Zoom in by delta zoom levels.

Throws ArgumentError if delta < 0. Fires a zoom change event.

void zoomIn([int delta=1]) {
 _require(delta >= 0, "delta >= 0 expected, got $delta");
 if (delta == 0) return;

 //TODO: check for max zoom level
 var oldZoom = _zoom;
 _zoom+= delta;
 render();
 notify("zoom", oldZoom, _zoom);
}

void zoomOut([int delta = 1]) #

Zoom out by delta zoom levels.

Throws ArgumentError if delta < 0. Fires a zoom change event.

void zoomOut([int delta=1]) {
 if (delta < 0) throw new ArgumentError("delta >= 0 expected, got $delta");
 if (delta == 0) return;
 var oldZoom = _zoom;
 _zoom = math.max(0, _zoom - delta);
 render();
 notify("zoom", oldZoom, _zoom);
}

Point2D zoomPlaneToMap(Point2D p) #

Transforms coordinates p in the current map zoom plane to projected coordinates

Point2D zoomPlaneToMap(Point2D p) {
 var zp = zoomPlaneSize;
 var w = _crs.projectedBounds.width;
 var h = _crs.projectedBounds.height;
 return p.scale(sx: w/zp.width, sy: h/zp.height)
     .translate(dx:-w/2, dy:-h/2)
     .flipY();
}

Point2D zoomPlaneToViewport(Point2D p) #

Transforms coordinates in the current map zoom plane to viewport coordinates

Point2D zoomPlaneToViewport(Point2D p) {
var centerOnZoomPlane = mapToZoomPlane(earthToMap(center));
return (p - centerOnZoomPlane).toInt() + (viewportSize / 2);
}