This c# program reads in one geo-referenced source image and automatically produces a set of 256x256 tiles ready for use in a custom map layer or overlay. The code uses some functions from the Java Tile Utilities on the MAPKI Tile utility code in Java. You can download a free copy of c# from Microsoft [1] .
The input image needs to have an ESRI format World File [2] and be in WGS84 geographic coordinates, that is each pixel is a fixed size in latitude and longitude. Global Mapper [3] is a great tool for producing suitable input images from a variety of sources such as SRTM terrain data [4]. I have used it together with the code here to generate the terrain overlays that you can see at [5]
The code as shown cuts tiles at the source image's natural Google zoom level and two zooms either side (no interpolation / smoothing yet). As programmed the code only writes tiles that are fully inside the source image. The iteration finds partially intersecting margin tiles but does not output them. Changing the code to include the margin tiles would be easy, putting black or transparent in the pixels that have no equivalent in the source.
On your page's java script, use a function like this to supply tiles in your custom layer.
Code: Select all
function customGetTileUrl(a,b) {
var f = "http://www.mysite.com/mytiles/z" + b + "x" + a.x + "y" + a.y + ".jpg";
return f;
}
To create a hybrid tile layer with the Google's MAP use code like this (11 and 12 are min and max zoom levels for your tiles)
function loadLayer {
var tilelayers = new Array();
tilelayers[0] = G_NORMAL_MAP.getTileLayers()[0];
tilelayers[1] = new GTileLayer(copyrightCollection , 11, 12);// see MAPKI for copyright management
tilelayers[1].getTileUrl = customGetTileUrl;
tilelayers[1].getOpacity = function () {return 0.5;};
var custommapmap = new GMapType(tilelayers, new GMercatorProjection(13), "MyTiles+Map", {errorMessage:"No data available"});
map.addMapType(custommapmap);
}
Code: Select all
using System;
using System.Collections.Generic;
using System.Text;
namespace TileCutter
{
/**
* A set of simple routines to provide information about google tiles (API v2).
* Internally a sort of offset mercator projection is used (as per google), this places the origin (0,0)
* at the top left and goes to +1,+1 at the bottom right.
* A proper mercator would have 0,0 in the middle and range from -0.5,-0.5 bottom left to +0.5,+0.5 top right.
*/
public class GoogleTileUtils
{
public static int TILE_SIZE = 256;
/**
* Returns the pixel offset of a latitude and longitude within a single typical google tile.
* @param lat
* @param lng
* @param zoom
* @return
*/
public static System.Drawing.Point getPixelOffsetInTile(double lat, double lng, int zoom)
{
System.Drawing.Point pixelCoords = toZoomedPixelCoords(lat, lng, zoom);
return new System.Drawing.Point(pixelCoords.X % TILE_SIZE, pixelCoords.Y % TILE_SIZE);
}
/**
* returns a Rectangle2D with x = lon, y = lat, width=lonSpan, height=latSpan
* for an x,y,zoom as used by google.
*/
public static System.Drawing.RectangleF getTileRect(int x, int y, int zoom)
{
int tilesAtThisZoom = 1 <<zoom> 180)
{
lng -= 360;
}
lng /= 360;
lng += 0.5;
lat = 0.5 - ((Math.Log(Math.Tan((Math.PI / 4) + ((0.5 * Math.PI * lat) / 180))) / Math.PI) / 2.0);
return new System.Drawing.PointF((float)lng, (float)lat);
}
/**
* returns a point that is a google tile reference for the tile containing the lat/lng and at the zoom level.
* @param lat
* @param lng
* @param zoom
* @return
*/
public static System.Drawing.Point toTileXY(double lat, double lng, int zoom)
{
System.Drawing.PointF normalised = toNormalisedPixelCoords(lat, lng);
int scale = 1 << zoom;
// can just truncate to integer, this looses the fractional "pixel offset"
return new System.Drawing.Point((int)(normalised.X * scale), (int)(normalised.Y * scale));
}
/**
* returns a point that is a google pixel reference for the particular lat/lng and zoom
* @param lat
* @param lng
* @param zoom
* @return
*/
public static System.Drawing.Point toZoomedPixelCoords(double lat, double lng, int zoom)
{
System.Drawing.PointF normalised = toNormalisedPixelCoords(lat, lng);
double scale = (1 << zoom) * TILE_SIZE;
return new System.Drawing.Point((int)(normalised.X * scale), (int)(normalised.Y * scale));
}
}
/**
* routines to read in an ESRI world file for a geographic projection
*/
public class WorldFile
{
//returns a PointF with x = left , y = top , width = units per pixel x, height = units per pixel y
// for a Geo file this is lon , lat, degrees per pixel lon, degrees per pixel lat
public static System.Drawing.RectangleF readWorldFile(string file)
{
try
{
System.IO.StreamReader sr = new System.IO.StreamReader(file);
string[] lines = new string[6];
for (int i = 0; i <6> GoogleTileUtils.TILE_SIZE)
{
pixIn360 /= 2;
zoom += 1;
}
// iterate around the natural zoom
for (int z = (zoom - 2); z <zoom>= 0) && (z <= 17))
{
// find the intersecting google tiles
//zoom 0 is 1 tile,
//zoom 1 is 4 tiles etc
System.Drawing.Point tbl = GoogleTileUtils.toTileXY(inGeoRect.Bottom, inGeoRect.Left, z);
System.Drawing.Point ttr = GoogleTileUtils.toTileXY(inGeoRect.Top, inGeoRect.Right, z);
// for each intersecting tile
int tilesAtThisZoom = 1 << z;
for (int x = tbl.X ; x <= ttr.X ; x++)
{
for (int y = ttr.Y; y <= tbl.Y ; y++)
{
System.Drawing.Bitmap bm = new System.Drawing.Bitmap(GoogleTileUtils.TILE_SIZE, GoogleTileUtils.TILE_SIZE, gr);
bool bWrite = true;
// get pixels from the input image to go in the output tile
for (int py = 0; (py < bm.Height) && (bWrite); py++)
{
//get lat of this pixel
float my = (y + (((float)py) / ((float)GoogleTileUtils.TILE_SIZE))) / tilesAtThisZoom;//normalised mercator height
float pla = (float)((180 / Math.PI) * ((2 * Math.Atan(Math.Exp(Math.PI * (1 - (2 * my))))) - (Math.PI / 2)));
//y coord of pixel in input image
int iy = (int)Math.Round(inImage.Height * ((pla - inGeoRect.Bottom) / (-inGeoRect.Height)));
iy = inImage.Height - iy;
for (int px = 0; (px <bm>= 0) && (ix <inImage>= 0) && (iy < inImage.Height))
bm.SetPixel(px, py, inImage.GetPixel(ix, iy));
else
bWrite = false;//only want complete tiles
}
}
// make up the output tile's filename
// save the output tile
if (bWrite)
{
string fn = "z" + z.ToString() + "x" + x.ToString() + "y" + y.ToString() + ".jpg";
EncoderParameters eps = new EncoderParameters(1);
eps.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, (long)90);
ImageCodecInfo ici = getCodec("image/jpeg");
bm.Save(outPath + "/" + fn + ".jpg", ici ,eps);
//bm.Save(outPath + "/" + fn + ".png",ImageFormat.Png);
//bm.Save(outPath + "/" + fn + ".gif", ImageFormat.Gif);
//ici = getCodec("image/tiff");
//System.Drawing.Imaging.Encoder enc = System.Drawing.Imaging.Encoder.Compression;
//eps.Param[0] = new EncoderParameter(enc,(long)EncoderValue.CompressionLZW);
//bm.Save(outPath + "/" + fn + ".tif", ici, eps);
}
}
}
}
}
}
private static ImageCodecInfo getCodec(string mimeType)
{
ImageCodecInfo[] encoders = ImageCodecInfo.GetImageEncoders();
for(int j=0;j<encoders.Length;j++)
{
if(encoders[j].MimeType == mimeType)
return encoders[j];
}
return null;
}
}
}
1. http://msdn2.microsoft.com/en-us/vstudio/aa700757.aspx
2. http://www.kralidis.ca/gis/worldfile.htm
3. http://www.globalmapper.com/
4. http://www2.jpl.nasa.gov/srtm/cbanddataproducts.html
5. http://www.bdcc.co.uk/mendip.html
I thought maybe some of the brighter guys running around these forums maybe could build on this idea and code base to create something useable for Falcon theater builders....