ZoomRadar Tiles Integration

Developer guide for integrating cloud tiles into your map application.


Introduction

  • Item Name : ZoomRadar Tiles Integration Guide
  • Item Version : v 1.0

Welcome to ZoomRadar's tiles integration guide. This guide will help to integrate cloud tiles into your map applicaiton.

It will also demonstrate the integration on two popular map engines: OpenStreetMaps and GoogleMaps.

OpenStreetMaps Demo

GoogleMaps Demo

Requirements

In order to use this guide you will need the following:

  1. A project featuring one of map engines.
  2. The map engine being used needs to support latitude/longitude to screen pixels and vice-versa conversions.
  3. Basic knowledge of coding and of geo-data.

Please visit ZoomRadar for more information.

Generating API Key

To acquire your key please visit Generating API Key page and fill in your information. Currently it's free of charge. Once submitted you will receive your key, save it.

Generate API Key

Testing your API key.

In order to test your key replace YOUR_API_KEY with your API key in the below query and open it.

https://builder.zoomradar.net/zoomradar_weather_map/tilesApi.php?key=YOUR_API_KEY

If it returns an XML (you may need to view the source as some browsers display it as blank) then your key is working, else please contact support by emailing to zoomradar@gmail.com.

Loading Tiles

Loading Recent Tiles

Use your generated API key to request latest tiles list using the bellow URL.

https://builder.zoomradar.net/zoomradar_weather_map/tilesApi.php?key=YOUR_API_KEY

This will return an XML array of images where each item contains a timestamps. Parse timestamps and save them for later use.

These timestamps represent most recent cloud frame images. In order to avoid synchrnoization issues remove latest two timestamps, since the servers might be still processing them.

If you're interested only in static image (no animation) then only the latest timestamp is necessary.

Sample Code

Below we'll use jQuery to load and parse the latest tile timestamp.

							$.ajax(
								{
									type: "GET",
									url: "https://builder.zoomradar.net/zoomradar_weather_map/tilesApi.php?key=YOUR_API_KEY",
									dataType: "xml",
									crossDomain: true,
									error: function (xhr, text, thrown) {
										// error management
									},
									success: function (xml) {
										var imageNames = [];
										$(xml).find('image').each(function () {
											var str = $(this).attr('path');
											str = str.split("/");
											if (str.length == 2)
												str = str[1].split(".");
											if (str.length == 2) {
												imageNames.push(str[0]);
											}
										});

										//Remove last frame to give the server time to sync images
										imageNames.shift();
										imageNames.pop();

										//Output latest image
										console.log(imageNames[0]);
									}
								}
							);
						

Geo Constants

Radar Bounds

The radar cloud is a large image that covers entire US map. Since displaying such a large image is practically impossible on any map engine our main goal is to split it into small tile pieces and only display necessary tiles.

This is why understanding basics behind the radar cloud and the tile separation is important.

Let's start from importing the cloud radar bounds:

							var radarBounds: {
								feedMaxLat: 50.303131,
								feedMinLat: 22.063271,
								feedMaxLon: -64.575134,
								feedMinLon: -128.424866
							};
						

Radar Sizes

And also let's import radar dimensions in pixels:

							var radarSize = {
								width: 28431,
								height: 15919
							};
						

Maximum Zoom Level

The radar cloud is not just being sliced into tiles, but it's also being scaled for different zoom levels. This makes the usage of tiles more adapted to different zoom levels of map engine. Zoom levels start at 1 and grow with powers of 2 (1, 2, 4, 8, etc). The zoom level 1 represents tiles sliced without any scaling. The zoom level 2 represents entire cloud scaled down by 2x both horizontally and vertically, and then sliced into tiles.

It's important to know maximum amount of supported zoom levels:

							var maxZoomLevel = 64;
						

Tile Sizes

Finally it's important to know each tiles size in pixels:

							var tileSizes: {
								width: 350,
								height: 350
							};
						

Tile Coordinates

Additionally there's a need to import pre-calculcated geo-coordinates for each tiles at each zoom level. In order to do it import the bellow JSON.

							http://builder.zoomradar.net/zoomradar_weather_map/national_tile_coordinates.json
						

With all these constants ready we can go to next step.

Geo Methods

There are two type of methods which are necessary for radar clouds to be displayed on your map enginge.

  1. A method to display image layers. This assumes binding an image source into latitude/longitude bounds and map engine being able to render it on top of actual map, as well ass moving it while dragging or zooming the map.
  2. Two methods to convert a given latitude/longitude coordinates into screen's relative x/y pixel coordinates and the vice-versa. This is necessary to calculcate necessary/visible tiles and to properly position them.

Image Overlay

Most engines support this natively. For example GoogleMaps already do this as displayed here.

However on other engines, such as on OpenStreetMaps you might need to slightly alter the existing overlay. Here's an example below:

							LeafMap.prototype.newGroundOverlay = function (url, sw, ne, opacity, clickable) {
							  var imageUrl = url, imageBounds = [{
								lat: ne.lat(),
								lng: ne.lng()
							  }, {
								lat: sw.lat(),
								lng: sw.lng()
							  }];

							  var overlay = L.imageOverlay(imageUrl, imageBounds, {
								opacity: opacity,
								zIndex: 2,
								alt: 'Weather pattern',
								interactive: true,
								crossOrigin: false,
								errorOverlayUrl: './leaflet/error_image.png', // you would put an error url to an image here
								className: 'image-one' //css
							  }).addTo(this.map);

							  return overlay;
							}
						

In case your engine doesn't support image overlays at all but it supports general HTML overlays you might need to code a custom overlay on your own.


Latitude/longitue to screen pixel coordinates

While most engines have a solution for this, usually some additional coding is necessary to prepare these methods.

GoogleMaps, for example, use so called "projection"-s to perform this conversion. Below are sample functions:

							//Screen pixels to latitude/longitude
							GmapMap.prototype.pixToLL = function (point) {
								var ne = this.map.getBounds().getNorthEast();
								var sw = this.map.getBounds().getSouthWest();
								var projection = this.map.getProjection();
								var topRight = projection.fromLatLngToPoint(ne);
								var bottomLeft = projection.fromLatLngToPoint(sw);
								var scale = 1 << this.map.getZoom();

								var newLatlng = projection.fromPointToLatLng(new google.maps.Point(point.x / scale + bottomLeft.x, point.y / scale + topRight.y));
								return newLatlng;
							}
							
							//Latitude/longitude to screen pixels
							GmapMap.prototype.llToPix = function (latLng) {
								var topRight = this.map.getProjection().fromLatLngToPoint(this.map.getBounds().getNorthEast());
								var bottomLeft = this.map.getProjection().fromLatLngToPoint(this.map.getBounds().getSouthWest());
								var scale = Math.pow(2, this.map.getZoom());
								var worldPoint = this.map.getProjection().fromLatLngToPoint(latLng);
								return new google.maps.Point((worldPoint.x-bottomLeft.x)*scale, (worldPoint.y-topRight.y)*scale);
							}
						



OpenStreetMaps, on the other hand, support this directly. Below are sample functions:

							//Screen pixels to latitude/longitude
							LeafMap.prototype.pixToLL = function (point) {
							  var topRight = this.map.latLngToLayerPoint(this.map.getBounds().getNorthEast());
							  var bottomLeft = this.map.latLngToLayerPoint(this.map.getBounds().getSouthWest());
							  var scale = parseInt(this.map.getZoom());

							  var newLatlng = this.map.layerPointToLatLng(
								L.point(point.x / scale + bottomLeft.x, point.y / scale + topRight.y)
							  );

							  return {
								lat: function () { return newLatlng.lat; },
								lng: function () { return newLatlng.lng; }
							  };
							}
							
							//Latitude/longitude to screen pixels
							LeafMap.prototype.llToPix = function (latLng) {
							  var topRight = this.map.latLngToLayerPoint(this.map.getBounds().getNorthEast());
							  var bottomLeft = this.map.latLngToLayerPoint(this.map.getBounds().getSouthWest());
							  var scale = parseInt(this.map.getZoom());
							  var worldPoint = this.map.latLngToLayerPoint({
								lat: latLng.lat(),
								lng: latLng.lng()
							  });

							  return L.point((worldPoint.x - bottomLeft.x) * scale, (worldPoint.y - topRight.y) * scale);
							}
						

With these methods and constants ready we can go to the next step.

Identifying Visible Tiles

Once we have constants and necessary methods ready it's time to identify visible tiles. In most situations, when user is zoomed in somewhere on the map, only small portion of the radar cloud is visible. And it is important to identify which tiles are located in the current view area.

Even more important is to detect which zoom level should be used for current view. For example if it's the entire US map then the most scaled down image tiles need to be used, on the other hand if it's zoomed to a street level then unscaled tiles need to be used.

Below we'll use mapEngine as a global variable containing all above constants and methods. Please change with corresponding variabls for your case.

Identifying Zoom Level

							//Identify current view bounds
							var topLeftCorner = mapEngine.llToPix(
								mapEngine.newLLPoint(mapEngine.radarBounds.feedMaxLat, mapEngine.radarBounds.feedMinLon)
							);
							var bottomRightCorner = mapEngine.llToPix(
								mapEngine.newLLPoint(mapEngine.radarBounds.feedMinLat, mapEngine.radarBounds.feedMaxLon)
							);
							
							//Identify target size (horizontal size of radar)
							var targetSize = Math.abs(bottomRightCorner.x - topLeftCorner.x);
							
							//Identify zoom level
							var zoomLevel = 1;
							while (targetSize < mapEngine.radarSize.width / zoomLevel / 2)
								zoomLevel *= 2;
							if(zoomLevel > mapEngine.maxZoomLevel) zoomLevel = mapEngine.maxZoomLevel;
						

This will identify the necessary zoom level for current view.


Identifying Visible Tiles

Next let's identify which tiles of the current zoom level are visible. That is which horizontal and vertical index ranges are visible.

							//Identify bounds
							var mapURLat = mapEngine.getBoundsN();
							var mapURLon = mapEngine.getBoundsE();
							var mapLLLat = mapEngine.getBoundsS();
							var mapLLLon = mapEngine.getBoundsW();

							//Identify pixel bounds of current view
							var mapUL = mapEngine.llToPix(
								mapEngine.newLLPoint(mapURLat, mapLLLon)
							);
							var mapLR = mapEngine.llToPix(
								mapEngine.newLLPoint(mapLLLat, mapURLon)
							);

							//Identify radar sizes in current view
							var currentRadarWidth = mapEngine.radarSize.width / zoomLevel;
							var currentRadarHeight = mapEngine.radarSize.height / zoomLevel;
							var correctionRatio = currentRadarWidth / targetSize;
							
							//Identify pixel bounds of tiles
							var minX = (mapUL.x - topLeftCorner.x) * correctionRatio;
							var minY = (mapUL.y - topLeftCorner.y) * correctionRatio;
							var maxX = (mapLR.x - topLeftCorner.x) * correctionRatio;
							var maxY = (mapLR.y - topLeftCorner.y) * correctionRatio;
							
							//Identify tile indices
							var tileMinX = Math.floor(Math.max(minX - mapEngine.tileSizes.width, 0) / mapEngine.tileSizes.width);
							var tileMinY = Math.floor(Math.max(minY - mapEngine.tileSizes.height, 0) / mapEngine.tileSizes.height);
							var tileMaxX = Math.floor(Math.min(maxX + mapEngine.tileSizes.width, currentRadarWidth) / mapEngine.tileSizes.width);
							var tileMaxY = Math.floor(Math.min(maxY + mapEngine.tileSizes.height, currentRadarHeight) / mapEngine.tileSizes.height);
						

With these indices identified we now know which tiles need to be loaded and displayed on the map.

Displaying Visible Tiles

Once we have identified visible tile indices alongside with the zoomlevel we can proceed to loading and to displaying them.

For each tile there's a specific URL and it's important to understand how tile URLs are being constructed. Each tile URL looks like this:

						http://miami.zoomradar.net/HiResNationalRadar4/us_radar_{timestamp}_zoom_{zoomlevel}_tile_{y}_{x}.png
						

Where following parameters represent:

  1. {timestamp} - The timestamp we've parsed earlier.
  2. {zoomlevel} - The zoomlevel we've identified earlier.
  3. {y} - The vertical index.
  4. {x} - The horizontal index.

A sample code would look like this:

							//Indices count 
							var xCnt = tileMaxX - tileMinX + 1;
							var yCnt = tileMaxY - tileMinY + 1;
							
							for (var x = 0; x < xCnt; x++) {
								for (var y = 0; y < yCnt; y++) {
									var tileUrl = "http://miami.zoomradar.net/HiResNationalRadar4/us_radar_" + mapEngine.imageNames[mapEngine.frameIndex] + "_zoom_" + zoomLevel + "_tile_" + (tileMinY + y) + "_" + (tileMinX + x) + ".png";
									var tileCoordinate = mapEngine.tileCoordinates[zoomLevel][tileMinX + x][tileMinY + y];
									mapEngine.newGroundOverlay(tileUrl, [tileCoordinate[2], tileCoordinate[1]], [tileCoordinate[0], tileCoordinate[3]]);
								}
							}
						

This will load and display necessary tiles on corresponding coordinates. Of course you might want to do some management by pushing them into an array.

Refreshing Tiles

It's important to refresh tiles on following actions/events:

  1. Map drag.
  2. Map zoom.
  3. Location change.

In general any action leading to current view change should trigger new visible tiles detection and then load and display new tiles while clearing previous tiles from the screen to prevent memory leaks.


Autorefresh

Also you might be interested in implementing an automatic timer (let's say 15 minutes) which will trigger tiles refreshment to keep them live. In order to do this the XML retreived by the API needs to be re-queried and all timestamps need to be updated.

Animation Loops

So far we've used only the latest timestamp for creating a static image. However one can use more than one timestamp to create radar animations.

OpenStreetMaps Animation Demo

In order to achieve this all you have to do is to use a timer interval to loop through desired timestamps and replace tile images.

Source Code

Finally, we want to share some source code which you're free to copy and to use in your projects.

OpenStreetMaps Source GoogleMaps Source

Copyright and license

Code released under the Apache 2.0 License.

For more information about copyright and license check ZoomRadar.com or email to zoomradar@gmail.com.