You are here

GIS Mapping in PHP(T)

Blog Terms: 
GIS Mapping in PHP: Part 1
Published online at


There are a number of great online GIS ( Geographic Information System ) solutions out there, ranging from Mappoint to Mapquest to some cool PHP projects. However, it is still nice to be able to be able plot you own data and put some basic mapping utilities on your site with a minimum of fuss and bother. Over the next couple of articles I will hopefully be able to give you a few pointers on how to create you own basic GIS system for free. I do not claim to be a GIS expert, and there may be better ways to do things, but hopefully these articles will give you a start in the right direction.

In this article we will start with the absolute basics - plotting a single location on a pre drawn map of the world. Later articles will go into drawing vector maps, drawing roads and rivers and labeling objects all using the principles in this article.

For this article you will need:

PHP with GD installed
A pair of scissors
An adult to help you

Finding your place in this world

To plot an object on any map, be it a house or a herd of caramelized Wombats, the object needs to be geocoded - that is have a map coordinate allocated to it. For all examples we used a standard longitude (long) and latitude (lat) coordinate. There a numerous other coordinate systems out there - some a lot more accurate, but this is the most common, the easiest to plot and suits our needs. The downside of directly plotting long/lat coordinates is that you will get distortion as you near the poles. However, for our uses this doesn't pose a problem.

The two coordinates of a longitude and latitude refer to the angle in degrees from the equatorial plane of the earth, both up and down. Longitude lines extend from pole to pole giving us an "X" coordinate, Latitude lines give us the "Y" coordinate. These can either be written as a decimal value ( ie 23.323232 ) or as degrees, minutes and seconds ( D'M'S ). For storing our coordinates we used the decimal version as it's a lot easier and saves a lot of processing time. There are a number of sites with Javascript utilities which will convert coordinates from D'M'S ( degrees, minutes and seconds ) to the decimal format. I happen to use a Sharp EL-546 scientific calculator which has the function built in.

There are a number of ways to finding the long/lat of a point in the world, however a quick and easy way is to used Microsofts online GIS service Mappoint ( ). Find your location using Mappoint search utilities and make sure it is in the center of the map. Right mouse click on the map, and you will find long/lat coordinates embedded in the URL of the map image. It is also worth looking at, plus a number of government operated environmental sites will have map data which you can download for free. Those of you who have Mapinfo will also find very useful geocoded data on the sample discs which come with it.

The point we are plotting for this article is my 64 bedroom mansion, located in the bustling urban metropolis which is Prince Edward Island, Canada. Using Mappoint I have discovered my location is:

Longitude: -63.10774861954596
Latitude: 46.2899306519141

We now need a base map to plot our point on. In future articles we will generate this ourselves, however to start we will use a simple pre drawn JPG file. The base map we are using is called earth_310.jpg, and is and view of the earth in what is called a Cylindrical projection.

The original of this image can be found in various forms all over the web in various sizes and scales. A cylindrical projection is the simplest projection to plot long/lat coordinates onto, again with a minimum of conversion needed reducing processor overhead. For our purposes you can simply right click the image and "Save picture as ..." to your hard drive.

The base map is scaled to 310x155 for ease of use, but you can rescale this map to any size.

We are now ready to generate the code to plot our point.

Plots and schemes

The basic steps for generating the map are:

Load the background map.
Convert and scale the long/lat coordinates to screen coordinates.
Plot the point.
Return the finished map as an image.

To convert the long/lat to screen coordinate we have a created a function called getlocationcoords. This takes the longitude and latitude coordinates plus the size of the base map and return the screen coordinates in an associative array. $width and $height are calculated from the size of the background image. In future projects these variables are used scale the map and set the zoom level.

function getlocationcoords($lat, $lon, $width, $height)
   $x = (($lon + 180) * ($width / 360));
   $y = ((($lat * -1) + 90) * ($height / 180));
   return array("x"=>round($x),"y"=>round($y));

Once the coordinates have been converted it's as simple as drawing a rectangle on the base map using the returned array to mark our location.

So, the code to create the map:


// These are the coordinates the location we wish to plot.
// These are being passed in the URL, but we will set them to a default if nothing is passed.

if(empty($long))$long = -63.10774861954596;
if(empty($lat)) $lat = 46.2899306519141;

// First we load the background/base map. We assume it's located in same dir as the script.
// This can be any format but we are using JPG in this example
// We will also allocate the color for the marker

$im = imagecreatefromjpeg("earth_310.jpg");
$red = imagecolorallocate ($im, 255,0,0);

// Next need to find the base image size.
// We need these variables to be able scale the long/lat coordinates.

$scale_x = imagesx($im);
$scale_y = imagesy($im);

// Now we convert the long/lat coordinates into screen coordinates

$pt = getlocationcoords($lat, $long, $scale_x, $scale_y);

// Now mark the point on the map using a red 4 pixel rectangle


// Return the map image. We are using a PNG format as it gives better final image quality than a JPG

header ("Content-type: image/png");


To load the map call the PHP code from an IMG tag, passing the longitude and latitude in the long and lat variables:

<IMG SRC="map_code.php?long=long&lat=lat">

You could easily modify this code to read long/lat coordinates from a database or a delimited text file and plot multiple points.


This is a very basic script. However the basic principle will allow you to draw very complicated maps.

The important part is the getlocationcoords function - once you have geocoded your data and have the routines to plot the points on the screen, the sky's the limit!

Simon Moss


GIS Mapping in PHP: Part 2
Published online at

In my previous article I described a very simple method of plotting longitude and latitude coordinates onto a drawn map of the world. With a very small amount of work modifying the script in the article - adding a FOR loop and some database code ) you could quite easily come up with a system like in a matter of minutes.

The main limitation with this system is that you are restricted as to what background you can use. You have to have a predrawn raster image drawn in the correct scale and projection for the information you want to display. What if all you want to show is the location of a house in Prince Edward Island? Where do you get that raster image? What if the house is on the edge of the island and you want it centered on the screen? What if you want to zoom in and out without a significant loss of quality? What if goats overthrow the goverments of the world and forced us to wear itchy beards?. The answer to these questions is to draw your own background images dynamically. You can then scale, move, center, color to your hearts content. As for the goats .. well .. be nice to them and they will be nice to you.

What's the point of Vector?

So then first step is to get and process the data for the background image. The data we need is called vector data ( as opposed to the Raster image we were viously dealing with ). This is normally stored as a list of points divided into regions which make up complete polygon shape of the state / province / country. These points are normally listed as longitude and latitudes, which is great because we already have a function from the previous article to convert long/lats into screen coordinates - getlocactioncoords. Vector maps allow for easy scaling and movement. You can rotate, stretch and skew with a minimum of fuss and bother.

Now the big problem is finding the vector data in the first place. You can buy this information, or you can find basic vector information around the web. www.geogratis.comis a good place to start. Also check the US/Canadian Enviromental sites. Remember, normally the purchased products will have a greater level of detail. Most of the world is available for free in 1:1000000 scale, including outline maps for US and Canadian states/provinces ... just give google a try. For those lucky lucky people with access to Mapinfo check the sample disks that come with it. You should find coverage for North America plus a geocoded US Gazeteer. Another good google keyword to keep in mind is DCW - the digital chart of the world. This gives you polygons for every country in the world, plus more detailed information for North America ( states and provinces ). You can also find lakes, rivers, roads and outlines of major population areas.

Ok, so you've downloaded a chunk of data. We now run into our next problem. Most of the vector data on the web is in Arcview format ( .E00 ). We need to load this data into PHP in to variables which we can them read and manipulate. We need to be able to differentiate between the different types of vector information and also ( in later stages ) be able to assign colors and formating to the data.

The E00 format is text based ( in uncompressed form ) and there a few PHP routines out there that will load the various elements of an E00 file and draw it onto an image. However, in this article I will have -converted all my E00 into Mapinfo MIF format. "Why?" you ask. "You swine, we thought you were going to tell us how to load E00 directly" you scream. Well, for starters the MIF format is very easy to read and load in a script, and also allows for various attributes to be associated with the polygon region ( which although we ignore could be usefull to some people ). The other reason is that I use Mapinfo is that if you are serious about GIS, Mapinfo is a very handy tool to have. I'm not on a commission ( though a free mug would be nice ) , but from experience a proper GIS application becomes invaluable when dealing with large amounts of information and will allow you to export a wide variety of GEO vector data into the PHP scripts I've given you. It is great for splitting large vector regions into smaller, more manageable chunks ( think Quebec, a bit map at the best of times ) - the smaller the chunks of data you deal with the faster the processing time and the less work PHP will have to do. The other benefit is being able to align downloaded data and "triming" regions which are out of alignment. Remember free data from different sources will always have different levels of accuracy. Being able to align and trim data to a base map will make your maps look much more professional. Remember PHP will never be able to offer the complete functionality a true GIS application can, so if you are serious about mapping its worth splashing out on a licence on good GIS tool.

For those without access to GIS tools ( c'mon guys, there are some free utilities out there ), the import routine provided in this article could easily be modified to read from E00 files, and if I have time I will add the routine to do this in another article at a later date. If someone wants to do one and let me see it, even better. I will see if I can incorporate it.

Anyho, back to getting our data in.

Getting it all in.

We have downloaded an outline map of PEI from . I won't give to the exact link as its good for you to browse around and get an idea of what data is available ( sorry, thats the school teacher in me - just remember to look for data in GEOG format, not LAMB. I could tell you why, but I'd have to shoot you.)

Next we have convert our E00 data into Mapinfo MIF using Mapinfos Universal Translator ( or some free utility on the web ). Although it doesn't really matter, try and keep all your projections constant when converting the data. Though in the MIF file the coordinates will look the same and will work for us, if you use different projections it can get messy if you ever decide to edit the files in Mapinfo at a later date.

So lets get the data into PHP. For this article our mission is to plot the location of my house on an outline map of Prince Edward Island. Why Prince Edward Island? Well, I live there and it is also has a smaller polygon count compared to other provinces. I'm gonna be nice to you all .... download the PEI MIF file from the downloads section at the bottom of this page.

Sit down, have a piece of cake and get ready for the next part.

At it's most basic ( and minus a bunch of header information ) the MIF format looks like this:



etc etc

ie ( a 'Region' type is basically a set of polygons )

Region 1
-63.778904 46.515854
-63.790916 46.518894
-63.796062 46.524384
Region 2
-63.778904 46.515854
-63.790916 46.518894
-63.796062 46.524384
-63.778904 46.515854
-63.790916 46.518894
-63.796062 46.524384
-63.796062 46.524384

E00 has a similar system of storing data but it involves a slightly more long winded process to get the data out.

For the outline polygons of states and provinces, we only really deal in the vector type of "REGION" (a polygon). MIF files can also contain PLINES ( a non-filled polygon ) and LINES which our importer can handle, plus a number of other shape types, which we don't need or care about. A point to note at this stage is that there are two types of Polygon - outside and inside. Basically one type of polygon is a filled area, the other is a hole - think of a donut. One polygon is the donut, the other is the hole. Due to processing limitations, we don't really deal with inside polygons. In later stages we come up with some cheats to get around this, especially when it comes to lakes and rivers.

To load the file, we just loop through each line read from the file. When we detect the start of a region ( by the presence of a string naming the type of vector object ), we create a new array and start to parse the coordinates into it. The polygons are stored in PHP in a array of associative arrays, with each associative array containing the type of vector object we have, the number of points in the object, the coordinates of the bounding rectangle and a string containing a space delimited list of coordinates.

At the moment we will load the vector data from a Mapinfo MIF file into the arrays each time we run the script. This is not the best way to do it for large amounts of data - however, it is the simplest method for the needs of the article. In later articles the same function will be used to prepare the data for import into POSTGRESQL.

The import script is made up of two functions, LoadMIF and GetPolyString.

function LoadMIF($file)

//Loads MIF file into a set of arrays.

// Open the file

$hfile = fopen("$file", "r");
$polygons = array();
$in_data = false;

// Read through the file until we hit the end

while (!feof($hfile))

$line = strtoupper(fgets($hfile, 1024));

// You could do this with reg. expressions. I hate them. So there.
// The DATA tag tells us we have got past the header info
// and are into the vector data proper.

else if($in_data)

// Are we a LINE? NB we don't plot these in this article.

$array = explode(" ",$line);
$poly_info = array();
$poly_info["min_long"] = $long = trim($array[1]);
$poly_info["min_lat"] = $lat = trim($array[2]);
$poly_info["max_long"] = $long_to = trim($array[3]);
$poly_info["max_lat"] = $lat_to = trim($array[4]);
$poly_info["vector_type"] = 1;
$poly_info["poly_count"] = 2;
$poly_info["poly_string"] = "$long $lat $long_to $lat_to";

// Are we a PLINE ( poly-line: A hollow polygon )

else if(substr($line,0,5)=="PLINE")
$array = explode(" ",$line);

// Get all the points in this polygon
// The first word on the line always stores
// the number of points in the polygon

$poly_info = GetPolyString($hfile,$array[1]);
$poly_info["vector_type"] = 2;

// Are we a region ( a filled polygon )

else if(substr($line,0,6)=="REGION")
$line = fgets($hfile, 1024);

// Again, get all the points in this polygon
// The first line always stores the number of points in the polygon

$poly_info = GetPolyString($hfile,$line);
$poly_info["vector_type"] = 3;
$polygons[] = $poly_info;
return $polygons;

function GetPolyString($hfile,$poly_count)
$ret_vector = array();
$ret_vector["min_long"] = 9999999;
$ret_vector["min_lat"] = 9999999;
$ret_vector["max_long"] = -9999999;
$ret_vector["max_lat"] = -9999999;
$ret_vector["poly_string"] = "";
$ret_vector["poly_count"] = $poly_count;

// Loop though the coordinates
// Each line contains the long. and lats. coordinates
// delimited by a space.

$line = fgets($hfile, 1024);
$array = explode(" ",$line);
$long = $array[0];
$lat = $array[1];
$ret_vector["min_long"] = min($long,$ret_vector["min_long"]);
$ret_vector["min_lat"] = min($lat ,$ret_vector["min_lat"]);
$ret_vector["max_long"] = max($long,$ret_vector["max_long"]);
$ret_vector["max_lat"] = max($lat ,$ret_vector["max_lat"]);
if(!empty($ret_vector["poly_line"]))$ret_vector["poly_line"] .= " ";
$ret_vector["poly_line"] .= "$long $lat";
return $ret_vector;

To use: $polygons = LoadMIF("{filename}");

There are some parts of these functions which do not seem to have any purpose whatsoever. Just relax and don't have a cow. In the greater scheme of things all will become clear.

Ok, so now we have the outlne of PEI loaded into memory. We should ( in the case of PEI ) have an array of 33 associative arrays containing all the information we need to draw the map.

Putting PEI on the map

The steps we need to take for drawing are:

Work out how big we want the final JPEG is going to be
Work out where the outline of PEI is to be centered
Work out the scale of the outline map.

We'll go straight the code which I'll disect later:

// Lets load the MIF file into our array

$polygons = LoadMIF("1.mif");
// This is the width of our final image

$image_sx = 400;
// This is the height of our final image

$image_sy = 400;

//This is the scale/zoom level if not parsed.

if(empty($scale))$scale = 35000;

// Next we set our base object we want to plot and center on

$my_long = -63.10774861954596;
$my_lat = 46.2899306519141;

// Set the correct scale for use in getlocationcoords

$sx = 2 * $scale;
$sy = $scale;

// Now we find out what screen coordinates the long/lat
//coordinates are at based on a complete map of the world

$center = getlocationcoords($my_lat, $my_long, $sx,$sy) ;

// Based on the size of the final image, we work out the
// amount we will need to add/subtract from the screen
// coordinate that will be calculated later to center our point
// on the final image.

$min_x = $center["x"] - ($image_sx / 2);
$min_y = $center["y"] - ($image_sy / 2);

// So lets create our image, and also allocate some colors to
// make everything look purdy.

$im = imagecreate($image_sx,$image_sy);
$land = imagecolorallocate ($im, 0xF7,0xEF,0xDE);
$sea = imagecolorallocate ($im, 0xB5,0xC7,0xD6);
$red = imagecolorallocate ($im,0xff,0x00,0x00);

// Lets now draw out inital background .. the mighty ocean.
// You could also use a drawn "sea scape" image if you
// wanted things to look a little different.

// Now we loop through the array of arrays getting each polygon
// in turn

foreach($polygons as $poly)
$converted_points = array();

// Each vector objects is stored as a space delimited string
// {long} {lat} {long} {lat} etc etc.
// So we explode these points into a temporary array for
// easy conversion to screen coordinates.

$points = explode(" ",$poly["poly_string"]);
$number_points = count($points);
$i = 0;

// Get each long/lat in turn. Convert it to screen coordinates
// Then subtract the "world screen" coordindate of our base object
// ( our house ) so the polygon will be centered around it.

$lon = $points[$i];
$lat = $points[$i+1];
$pt = getlocationcoords($lat, $lon, $sx, $sy);
$converted_points[] = $pt["x"] - $min_x;
$converted_points[] = $pt["y"] - $min_y;

// Then use GD to draw the polygon. We divide the number of points by
// 2 as this is the actually true number of points in the array


// Next center our base object

$pt["x"] = $center["x"] - $min_x;
$pt["y"] = $center["y"] - $min_y;

// And plot it in the middle of the map


// Set the headers and return the image header("Content-type: image/png");



And thats it ... you have drawn a map of PEI and plotted my house on it.

By altering the value of $scale you can zoom in and out. By adding/subtracting variables to $min_x and $min_y you can provide scrolling. You could also just save the image and use it as a base map for other maps or as a server side cache to speed things up. Tidy it up in Photoshop or add custom symbols for even better base maps with no copyright as you made it!!!!. Take it out to dinner and let it meet your parents.

Now althougth this map just shows PEI, there is no reason why you could not have a bunch of MIF files ( or one big one ) containing the whole of North America, or even the world. Then you have a scalable, scrollable map of the freakin' world! But before you go off celebrating, there are a few things to remember.

Don't stress out PHP

First and formost is speed. We love PHP and we know it does its best, but we are not far off doing some major number crunching here. PEI is the smallest province around ( awwww .. cutie!!! )... when you get to a complicated region like Quebec with 30000 polygons in it ( or even the world ) .. things really start to get slow ... very slow. These problems will be addressed in the next article as we let POSTGRESQL do some of the hard work, and we also talk about making a custom PHP module in C to do some of the donkeywork. However, as a work-around, consider the following options/modifications:

Make a bunch of scale dependant MIF/E00 file. You are looking at PEI at a scale of 100000 - you don't need all that coastline detail. So create another MIF file with less details, and load it dependant on a preset scale range ( ie pei_1_to_60000.mif, pei_60000_to_100000.mif and so on ).

We give you the max/min coordinates of a polygon when we load it in LoadMIF ( and you said that code wasn't being used .. shame on you ), feel free to use them. If a polygon is outside your display, don't bother with it. Don't convert it and don't display it! ( this is dealt with in the next article along with a "reverse" getlocationcoords for this purpose ). This is where a GIS tool comes in handy. Calculate the scale/size you use most often and split your map into chunks that just cover that area.

Then comes memory. Be mindfull of PHPs memory usage. The default 8meg allocated to a script on most PHP installations is not enough to handle a large MIF file with thousands of polygons ( like British Columbia ). POSTGRESQL will cure some of this, but try to split large maps into smaller chunks. You can then filter them in the LoadMIF program ( with some tweaking ).


So, there you have it. Next time we learn how to get this data into and out of POSTGRESQL. You want roads? Where we're going we don't need roads, but I show you how to plot them anyway. And maybe we'll do some work on caching ... you never know.

Simon Moss


GIS Mapping in PHP: Part 3

This article is a brief note on doing the reverse of the functions described in Article 1. The first article showed how to plot a longitude and latitude coordinate on a map of the world. Part 3 explains how to calculate out what longitude and latitude someone was after when they click on the same map.

Article 1 introduced us to the getlocationcoords function, which was designed to retrieve screen coordinates for a cylindrical projection of the world. The reverse of this function is

function getlocationcoords_inv($x,$y, $width, $height)
   $lat = ((($y / $height) * 180) - 90) * -1;
   $lon =(($x / $width) * 360) - 180;
   return array($lat,$lon);

This function returns longitude and latitude from screen coordinates. Annoyingly as simple as that.

To examine what there is of it a little further - $x and $y are the screen coordinates, $width and $height store the height and width of the source image.

Remember that in this instance we assume the map is a COMPLETE map of the world ( as below ), not a part of it.

So lets put this into a little bit of code.

All we are going to do is create a HTML form and set our world map as an image map. When you click the map, your browser will POST the coordinates clicked back to our script. The coordinates are passed in ${image_name}_x and ${image_name}_y - image_name being defined by the name="image_name" tag within the <input> markup. We then simply parse the 2 coordinates through getlocationcoords_inv, along with the width and height of the image. Along with most of the examples on this site, we assume for the sake of simplicity that register_globals is on.

The code, along with the other HTML needed, in its most basic form is:


   $width = 310;
   $height = 155;
   $values = getlocationcoords_inv($map_x,$map_y, $width,$height);

<form name="form1" method="post" action="">
<input name="map"
Longitude:<?php print($values[1]);?>
Latitude:<?php print($values[0]);?>

And thats it! See the working example at the end of this article.


This piece of code may be small, but it is perfectly formed. The simplicity is such that integrating it with some of the other examples on this site will allow you to create some seemingly complex applications.

You can plot major cities using the code from article 1, calculate objects within a certain distance from that town, display the coordinates in Degree, Minutes and Seconds and so much more, all with 50 lines of code.

The code can also be converted to Java ( I know, I've done it ) .. allowing for dynamic display of coordinates.

Longitude: 141.67741935484
Latitude: 38.903225806452

GIS Mapping in PHP: Part 4

Another article which is taking a brief step back in time. The main point of this example is to show how you can use the functions we defined eariler to plot a number of points on a map instead of just a single one.

We are storing the points we want to plot ( as it happens a list of volcanoes ) in a text file. The records are CR/LF delimited. The columns in the list are separated with a space ( 0x20 ). For example -

54.6025 160.2825
-0.4 -91.1
54.6025 160.2825
40.85 14.2
54.6025 160.2825
38.4 14.9666667
52.6111111 158.0472222

To load the file into an array we are using the file function. This PHP function will load a CR/LF delimited file directly into an array, saving us a lot of overhead. To extract the columns we explode each element in the array.

To finish, we just add a loop to convert and plot each point in the array using our mighty getlocationcoords. The code here has been reduced - all the files you need to run and generate the example image are included in the ZIP file downloadable from the link below.


// We are assuming getlocationcoords is included somewhere else.

// First we load the text file containing the coordinated

$coord_array = file("vol.txt");

// This is our source raster file. For this example we are
// using a larger file

$im = imagecreatefromjpeg("earth_620.jpg");

$red = imagecolorallocate ($im, 255,0,0);

$scale_x = imagesx($im);
$scale_y = imagesy($im);

// Loop though each element of the array loaded from the
// text file

foreach($coord_array as $value)

// Explode out the latitude and longitude coordinates

    $co = explode(" ",$value);

// And we have done all this before ......

    $pt = getlocationcoords($co[0], $co[1], $scale_x, $scale_y);

// Return the image as a JPEG - it would be @ 230k as PNG!

header("Content-Type: image/jpeg");