You are on page 1of 6

<?

php

# Ported from Mark McClure's PolylineEncoder.js script.


// PolylineEncoder.js copyright Mark McClure April/May 2007
//
// This software is placed explicitly in the public
// domain and may be freely distributed or modified.
// No warranty express or implied is provided.
//
// This module defines a PolylineEncoder class to encode
// polylines for use with Google Maps together with a few
// auxiliary functions. Documentation at
//
http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/PolylineEncoder.html
//
// Google map reference including encoded polylines:
// http://www.google.com/apis/maps/documentation/
//
// Details on the algorithm used here:
// http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/
//
// Constructor:
// polylineEncoder = new PolylineEncoder(numLevels?,
// zoomFactor?, verySmall?, forceEndpoints?);
// where numLevels and zoomFactor indicate how many
// different levels of magnification the polyline has
// and the change in magnification between those levels,
// verySmall indicates the length of a barely visible
// object at the highest zoom level, forceEndpoints
// indicates whether or not the endpoints should be
// visible at all zoom levels. forceEndpoints is
// optional with a default value of true. Probably
// should stay true regardless.
//
// Convenience classes and methods:
// * PolylinePoint
// Constructor:
// myLatLng = new PolylinePoint(lat,lng);
// The dpEncode* functions expect points in the
// form of an object with lat and lng methods. A
// GLatLng as defined by the Google Maps API does
// quite nicely. If you're developing a javascript
// without loading the API, however, you can use
// a PolylinePoint for this purpose.
//
// Lower level methods
// PolylineEncoder.dpEncodeToJSON(points,
// color?, weight?, opacity?)
// Returns a legal argument to GPolyline.fromEncoded.
// //
// PolylineEncoder.dpEncode(points);
// This is where the real work is done. The return value
// is a JSON object with properties named encodedLevels,
// encdodedPoints and encodedPointsLiteral. These are
// strings which are acceptable input to the points and
// levels properties of the GPolyline.fromEncoded
// function. The encodedPoints string should be used for
// maps generated dynamically, while the
// encodedPointsLiteral string should be copied into a
// static document.
//
// The standard disclaimers, such as "use at your own risk,
// since I really don't have any idea what I'm doing," apply.

class PolylineEncoder {
private $numLevels;
private $zoomFactor;
private $verySmall;
private $forceEndpoints;
private $zoomLevelBreaks = array();

public function __construct ($numLevels=18, $zoomFactor=2, $verySmall=0.00001,


$forceEndpoints=true) {
$this->numLevels = $numLevels;
$this->zoomFactor = $zoomFactor;
$this->verySmall = $verySmall;
$this->forceEndpoints = $forceEndpoints;

for($i=0; $i<$numLevels; $i++) {


$this->zoomLevelBreaks[$i] = $verySmall * pow($zoomFactor, $numLevels-$i-1);
}
}

# The main function. Essentially the Douglas-Peucker algorithm, adapted for


encoding. Rather than simply
# eliminating points, we record their from the segment which occurs at that
recursive step. These
# distances are then easily converted to zoom levels.
public function dpEncode ($points) {
$absMaxDist = 0;
$stack = array();
$dists = array();

if(count($points) > 2) {
array_push ($stack, array(0, count($points)-1));
while(count($stack) > 0) {
$current = array_pop($stack);
$maxDist = 0;
$segmentLength = pow($points[$current[1]]->lat()-$points[$current[0]]-
>lat(),2) +
pow($points[$current[1]]->lng()-$points[$current[0]]->lng(),2);
for($i=$current[0]+1; $i<$current[1]; $i++) {
$temp = $this->distance($points[$i],
$points[$current[0]], $points[$current[1]],
$segmentLength);
if($temp > $maxDist) {
$maxDist = $temp;
$maxLoc = $i;
if($maxDist > $absMaxDist) {
$absMaxDist = $maxDist;
}
}
}
if($maxDist > $this->verySmall) {
$dists[$maxLoc] = $maxDist;
array_push ($stack, array($current[0], $maxLoc));
array_push ($stack, array($maxLoc, $current[1]));
}
}
}

$encodedPoints = $this->createEncodings ($points, $dists);


$encodedLevels = $this->encodeLevels ($points, $dists, $absMaxDist);
return array (
'encodedPoints' => $encodedPoints,
'encodedLevels' => $encodedLevels,
'encodedPointsLiteral' => str_replace('\\',"\\\\",$encodedPoints)
);
}

public function dpEncodeToJson ($points, $color='#0000ff', $weight=3,


$opacity=0.9) {
$result = $this->dpEncode(points);
return array (
'color' => $color,
'weight' => $weight,
'opacity' => $opacity,
'points' => $result['encodedPoints'],
'levels' => $result['encodedLevels'],
'numLevels' => $this->numLevels,
'zoomFactor' => $this->zoomFactor
);
}

# distance(p0, p1, p2) computes the distance between the point p0 and the
segment [p1,p2]. This could probably be replaced with
# something that is a bit more numerically stable.
private function distance ($p0, $p1, $p2, $segLength) {
$out = null;
if($p1->lat() === $p2->lat() && $p1->lng() === $p2->lng()) {
$out = sqrt(pow($p2->lat()-$p0->lat(),2) + pow($p2->lng()-$p0->lng(),2));
}
else {
$u = (($p0->lat()-$p1->lat())*($p2->lat()-$p1->lat())+($p0->lng()-$p1-
>lng())*($p2->lng()-$p1->lng()))/
$segLength;

if($u <= 0) {
$out = sqrt(pow($p0->lat() - $p1->lat(),2) + pow($p0->lng() - $p1-
>lng(),2));
}
if($u >= 1) {
$out = sqrt(pow($p0->lat() - $p2->lat(),2) + pow($p0->lng() - $p2-
>lng(),2));
}
if(0 < $u && $u < 1) {
$out = sqrt(pow($p0->lat()-$p1->lat()-$u*($p2->lat()-$p1->lat()),2) +
pow($p0->lng()-$p1->lng()-$u*($p2->lng()-$p1->lng()),2));
}
}
return $out;
}
# The createEncodings function is very similar to Google's
http://www.google.com/apis/maps/documentation/polyline.js
# The key difference is that not all points are encoded, since some were
eliminated by Douglas-Peucker.
private function createEncodings ($points, $dists) {
$encoded_points = "";

$plat = 0;
$plng = 0;
for($i=0; $i<count($points); $i++) {
if (isset($dists[$i]) || $i == 0 || $i == count($points)-1) {
$point = $points[$i];
$lat = $point->lat();
$lng = $point->lng();
$late5 = floor($lat * 1e5);
$lnge5 = floor($lng * 1e5);
$dlat = $late5 - $plat;
$dlng = $lnge5 - $plng;
$plat = $late5;
$plng = $lnge5;
$encoded_points .= $this->encodeSignedNumber($dlat) .
$this->encodeSignedNumber($dlng);
}
}
return $encoded_points;
}

# This computes the appropriate zoom level of a point in terms of it's distance
from the relevant segment in the DP algorithm. Could be done
# in terms of a logarithm, but this approach makes it a bit easier to ensure
that the level is not too large.
private function computeLevel ($dd) {
if ($dd > $this->verySmall) {
$lev=0;
while ($dd < $this->zoomLevelBreaks[$lev]) {
$lev++;
}
return $lev;
}
}

# Now we can use the previous function to march down the list of points and
encode the levels. Like createEncodings, we
# ignore points whose distance (in dists) is undefined.
private function encodeLevels ($points, $dists, $absMaxDist) {
$encoded_levels = "";
if ($this->forceEndpoints) {
$encoded_levels .= $this->encodeNumber($this->numLevels-1);
}
else {
$encoded_levels .= $this->encodeNumber(
$this->numLevels-$this->computeLevel($absMaxDist)-1);
}

for ($i=1; $i<count($points)-1; $i++) {


if (isset($dists[$i])) {
$encoded_levels .= $this->encodeNumber(
$this->numLevels-$this->computeLevel($dists[$i])-1);
}
}
if ($this->forceEndpoints) {
$encoded_levels .= $this->encodeNumber ($this->numLevels-1);
}
else {
$encoded_levels .= $this->encodeNumber(
$this->numLevels-$this->computeLevel($absMaxDist)-1);
}
return $encoded_levels;
}

# This function is very similar to Google's, but I added some stuff to deal with
the double slash issue.
private function encodeNumber ($num) {
$encodeString = "";
while ($num >= 0x20) {
$nextValue = (0x20 | ($num & 0x1f)) + 63;
$encodeString .= chr($nextValue);
$num >>= 5;
}
$finalValue = $num + 63;
$encodeString .= chr($finalValue);
return $encodeString;
}

# This one is Google's verbatim.


private function encodeSignedNumber ($num) {
$sgn_num = $num << 1;
if ($num < 0) {
$sgn_num = ~($sgn_num);
}
return($this->encodeNumber($sgn_num));
}
}

class PolylinePoint {
private $x;
private $y;

public function __construct ($y, $x) {


$this->x = $x;
$this->y = $y;
}

public function lat () {


return $this->y;
}

public function lng () {


return $this->x;
}

public static function pointsToLatLngs ($points) {


$latLngs = array ();
for($i=0; $i<count($points); $i++) {
array_push($latLngs, new PolylinePoint($points[$i][0],
$points[$i][1]));
}
return $latLngs;
}
}

# Sample.
$pl = new PolylineEncoder;
$encode = $pl->dpEncode (array(
new PolylinePoint (-87.912756, 41.665831),
new PolylinePoint (-87.900167, 41.723928),
new PolylinePoint (-87.880056, 41.731831),
new PolylinePoint (-87.838155, 41.732732),
new PolylinePoint (-87.833355, 41.732932),
new PolylinePoint (-87.823455, 41.733032),
new PolylinePoint (-87.813654, 41.732932),
new PolylinePoint (-87.798153, 41.710932),
new PolylinePoint (-87.797553, 41.688132),
new PolylinePoint (-87.794851, 41.630333),
)
);
print_r ($encode);
?>

You might also like