- Dart:Scalable Application Development
- Davy Mitchell Sergey Akopkokhyants Ivo Balbaert
- 1878字
- 2021-07-09 18:56:30
Application overview
The mapviewer
project is a Dart web application with an index.html
entry point that kicks off the application on main.dart
:
main() async { quakeMap = new MapDisplay(querySelector('#mapview'), width, height); await quakeMap.loadImage(); featPlotter = new FeaturePlotter(width, height, quakeMap.mapCtx); quakeMap.showPopup = showPopup; quakeUpdate(); querySelector('#zoombtn').onClick.listen(zoomMap); querySelector('#locatebtn').onClick.listen(locateUser); querySelector('#sortbtn').onClick.listen(sortFeatures); new Timer.periodic(new Duration(seconds: 60), quakeUpdate); new Timer.periodic(new Duration(milliseconds: 100), animationUpdate); }
The MapDisplay
class sets up the map on the web page (on the div
element with the ID mapview
) and the image is loaded in. Then, quakeUpdate()
is called to ensure the initial display of the map and data on the page.
Once the initial display is handled, the Zoom button is connected to the zoomMap
function. Then the two timers have handlers to deal with updating the quake data and the animation display. The FeaturePlotter
class handles the data and display updates and is used by quakeUpdate
and animationUpdate
.
Drawing the map image
The HTML5 canvas will host the live display. The background is a block-like map of the world; feel free to find and use a different version!
Let's have a look at the following screenshot:

The red lines represent 0 degrees of latitude and longitude.
Plotting on the map
The geometry point contains three pieces of data in a list called coordinates
. They are the longitude, latitude, and also the depth in kilometers of where the earthquake originated:
{type: Point, coordinates: [-117.4505005, 34.2316666, 13.31]}
To map these coordinates to the map, we use the method toPoint
in the FeaturePlotter
class in the file featureplotter.dart,
which performs the conversion as follows:
Point toPoint(double longitude, double latitude) { double x = 0.0; double y = 0.0; double hdeg = width.toDouble() / 360; double vdeg = height.toDouble() / 180; x = (width / 2) + (hdeg * longitude); y = (height / 2) - (vdeg * latitude); return new Point(x.toInt(), y.toInt()); }
The longitude and latitude is based on the spherical nature of the Earth, and the x
axis is 360
degrees wide with 0
being the mid-point. The y
axis is 180
with 0
being the midpoint at the equator. In terms of the GeoJSON data, the x
values are in the range of 180
to +180
and y
is from -90
to 90
.
Note
Dart, at the time of writing is at version 1.11 and you may be wondering if it will reach version 2 in the near future. Yes, it will!
During the first Dart Developer Summit, version 2 of Dart was mentioned for the first time by Lars Bak. While it was hinted that there may be some language changes, it was strongly emphasized these would only be made with tools to handle updating existing code. In Bak's words, "this is not Python!" This quote is a reference for the somewhat disruptive move from Python 2 to Python 3.
One key change that has already begun is the moving of some packages out of the SDK. This allows them to update on their own schedule. For example, dart:html
has to respond more quickly than the SDK update cycle to web browser changes.
Animating the display
The animationUpdate
handler set up in the main
function in the main.dart
file fires the FeaturePlotter
method updateDisplay
:
void animationUpdate([Timer t = null]) { featPlotter.updateDisplay(); }
This method in turn calls an update on each map indicator object instance, as shown in the following code snippet. The userLocation
will be covered later in this chapter:
void updateDisplay() { mapIndicators.forEach((mapIndicator) => mapIndicator.update()); userLocation.forEach((mapIndicator) => mapIndicator.update()); }
The MapIndicator
class in mapindicator.dart
handles the display and animation of the feature. The display will be a growing circle point with the maximum size of the point matching the magnitude of the earthquake event. The constructor and update method handle the task of keeping the plotted shape within range:
MapIndicator(this.x, this.y, this.ctx, this.magnitude) { maxWidth = (width + 1) + magnitude * 2; } void update() { width++; if (width == maxWidth) width = 0; draw(); }
This will cause the point width to grow upwards to the limit before being reset back to a value of 0
, as shown here:

The circle is drawn using the arc method of the HTML5 canvas:
void draw() { ctx ..beginPath() ..arc(x, y, width, 0, 2 * PI, false) ..fillStyle = colorPrimary ..fill() ..lineWidth = 1 ..strokeStyle = colorSecondary ..stroke(); }
The x
and y
specify the point where the feature will be drawn and w
specifies the radius. The next two parameters are the start and end angles, which are measured in radians, and in a circle there are 2 * PI
radians. The constant value for PI
is taken from the dart:math
library, which is amongst the imports of this class. The last parameter of this method is a flag to determine whether the arc should be drawn counterclockwise or not.
Fetching the data
The data is fetched as an HttpRequest
in the same manner as in the grid view project, and the fields we are interested in are put into the list quakepoints
before being filtered into the geoFeatures
list:
fetchFeatureList() async { geoFeatures.clear(); String data = await HttpRequest.getString(jsonSrc); List items; List quakePoints = []; try { items = JSON.decode(data); } catch (exception, stacktrace) { print(exception); print(stacktrace); } if (items != null) { items.forEach((String post) { Map feature = JSON.decode(post); List quakedata = [ feature['geometry']['coordinates'][0], feature['geometry']['coordinates'][1], feature['properties']['mag'], feature['properties']['place'], feature['properties']['type'], feature['geometry']['coordinates'][2] ]; quakePoints.add(quakedata); }); quakePoints.where((qp) => qp[4] == 'earthquake').forEach((qp) { geoFeatures.add(qp); }); } }
All the items returned are added to the quake points list, but we only want to add the earthquake
ones to the geoFeatures
list. The list may contain other types such as quarry blast
.
Using a where
clause, we can filter the list an item at a time. The list item is run through the function, and the list item at index number 4 is compared against the string earthquake
.
This is a very convenient method for getting only the required data and best of all it is available on any iterable object. On top of this, there are number of variants such as firstWhere
and lastWhere
that help to quickly get a reference to the data that matches a criterion.
Updating the map indicators
Once a list of geoFeatures
is available, it can be used to construct a list of mapIndicator
objects that will appear on the map. The entire list is cleared out each time so that the number of indicators on the map does not build up too much over time. Let's take a look at the following screenshot:
updateData() async { await fetchFeatureList(); mapIndicators.clear(); geoFeatures.forEach((List feature) { Point pos = toPoint(feature[0], feature[1]); MapIndicator mi; mi = new MapIndicator(pos.x, pos.y, ctx, feature[2].toInt()); mi.summary = "Magnitude ${feature[2]} - ${feature[3]}."; mapIndicators.add(mi); }); }
This function is marked async
as there is an await
being used for the fetchFeatureList
method call. Once the features are available, the mapIndicators
list is reset and a new one is constructed.
Mouse over popups
To give the user more details about the feature on the map, a popup appears at the top of the screen when the pointer is moved over a map indicator. To get the mouse pointer coordinates, a handler can be added to handle the mouse move events on the canvas element:
MapDisplay(CanvasElement mapcanvas, this.width, this.height) { mapctx = mapcanvas.getContext("2d"); mapcanvas.onMouseMove.listen(mouseMove); }
This is carried out in the MapDisplay
constructor and the method handles the triggering of the implementation function. This function is a field in the class that is set by the page using the map display:
void mouseMove(MouseEvent e) { if (showPopup != null) showPopup(e.client.x - 50, e.client.y - 90); }
The implementation of the showPopup
function is in main.dart.
It displays the initially hidden popup above the map and then populates it with the feature details:
void showPopup(int x, int y) { bool inHotspot = false; if (zoomed) { x = x ~/ 2; y = y ~/ 2; } featPlotter.hotspotInfo.forEach((Rectangle<int> key, String value) { if (key.containsPoint(new Point(x, y))) { inHotspot = true; querySelector('#popup').innerHtml = value; } }); if (inHotspot) querySelector('#popup').style.visibility = "visible"; }
If the map display is zoomed in, then the coordinates must be updated; this is performed with the ~/
division operator, which returns an integer value. This is faster than the equivalent (x/2).toInt()
and gives the required whole numbers for plotting:

The Map
containing the hotspotInfo
is iterated over with a convenient forEach
loop, giving both the dictionary key
and the respective value
. The current point of the mouse relative to the top-left corner of the canvas element is tested against each rectangle that defines the hotspot (the hotspot is the area on the map that will trigger the popup).
The hotspots are stored as a list of Rectangle<int>
objects generated when the data list is being updated by the FeaturePlotter
class. This class is from the dart:math
package:
void updateHotspots() { hotspotInfo.clear(); mapIndicators.forEach((MapIndicator mi) { Rectangle<int> rect = new Rectangle(mi.x - mi.maxWidth, mi.y - mi.maxWidth, mi.maxWidth * 2, mi.maxWidth * 2); hotspotInfo[rect] = mi.summary; }); }
The hotspot is defined as a square area around the map indicator when it is at its maximum width. The summary
property gives the text to be displayed. The screenshot of the quake map is as follows:

Hopefully, this can help you in improving your geographical knowledge and learning more about Dart!
When debugging in Dartium using a REST or other similar web service, it is possible to get extra logging in the console window of the browser's developer tools for XMLHTTPrequests
.
Bring up the context menu (right-click), and there is an option named Log XMLHttpRequests that displays the HTTP requests made by the web page in the console output window:
Tip
[-121.5371704, 36.8165016, 0.93] 129.91739911111114 177.278328 2.2222222222222223 3.3333333333333335 maxWidth 2 XHR finished loading: GET "http://127.0.0.1:8080/api/quake/v1/latest".
This can be invaluable when debugging a project and saves adding logging to every part of the code that makes an HTTP request.
Zooming into the display
The canvas element has a range of features for 2D drawing, and this includes a scaling feature that we can use to hone in on the most active part of the map, North America. In main.dart,
a flag exists called zoomed, defaulting to false
, that aids the application in keeping track of the current display. A button is bound to the zoom code implementation:
zoomMap(Event evt) async { if (zoomed == true) { zoomed = false; quakeMap.mapCtx.resetTransform(); quakeMap.mapCtx.scale(1, 1); } else { zoomed = true; quakeMap.mapCtx.scale(2, 2); } quakeMap.drawMapGrid(); }
The canvas is toggled between scaling settings, with a critical call to the method resetTransfom
. Without this, the scaling would not be back to normal. The entire map view must be redrawn immediately so that the user has a responsive experience. Let's have a look at the following screenshot:

If you watch this page for a while, you will find the California and Alaska feature embedded heavily in the data. As the pop-up code handles the change in scale, these are still available. Pressing the Zoom button a second time returns to the full world view.
- Dynamics 365 for Finance and Operations Development Cookbook(Fourth Edition)
- Java程序設計(慕課版)
- Mastering NetBeans
- .NET之美:.NET關(guān)鍵技術(shù)深入解析
- Hands-On Machine Learning with scikit:learn and Scientific Python Toolkits
- Vue.js快跑:構(gòu)建觸手可及的高性能Web應用
- Learning Continuous Integration with TeamCity
- Access 2010中文版項目教程
- IBM Cognos TM1 Developer's Certification guide
- C陷阱與缺陷
- Tableau Desktop可視化高級應用
- Jakarta EE Cookbook
- Learn Linux Quickly
- Maya Programming with Python Cookbook
- 三步學Python