100% found this document useful (6 votes)
2K views

PolylineEncoder Class PHP

Mark McClure posted a link to PHP code (written by Jim Hribar) for encoding Google Maps polylines, but it didn't utilize classes. Here's his code wrapped in classes with an example of how to use.

Uploaded by

mreall
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
100% found this document useful (6 votes)
2K views

PolylineEncoder Class PHP

Mark McClure posted a link to PHP code (written by Jim Hribar) for encoding Google Maps polylines, but it didn't utilize classes. Here's his code wrapped in classes with an example of how to use.

Uploaded by

mreall
Copyright
© Attribution Non-Commercial (BY-NC)
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 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