You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

410 lines
15KB

  1. <?php
  2. /**
  3. * staticMapLite 0.3.1
  4. *
  5. * Copyright 2009 Gerhard Koch
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. *
  19. * @author Gerhard Koch <gerhard.koch AT ymail.com>
  20. *
  21. * USAGE:
  22. *
  23. * staticmap.php?center=40.714728,-73.998672&zoom=14&size=512x512&maptype=mapnik&markers=40.702147,-74.015794,blues|40.711614,-74.012318,greeng|40.718217,-73.998284,redc
  24. *
  25. */
  26. error_reporting(0);
  27. ini_set('display_errors', 'off');
  28. Class staticMapLite
  29. {
  30. protected $maxWidth = 1024;
  31. protected $maxHeight = 1024;
  32. protected $tileSize = 256;
  33. protected $tileSrcUrl = array('mapnik' => 'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png',
  34. 'osmarenderer' => 'http://otile1.mqcdn.com/tiles/1.0.0/osm/{Z}/{X}/{Y}.png',
  35. 'cycle' => 'http://a.tile.opencyclemap.org/cycle/{Z}/{X}/{Y}.png',
  36. );
  37. protected $tileDefaultSrc = 'mapnik';
  38. protected $markerBaseDir = 'images/markers';
  39. protected $osmLogo = 'images/osm_logo.png';
  40. protected $markerPrototypes = array(
  41. // found at http://www.mapito.net/map-marker-icons.html
  42. 'lighblue' => array('regex' => '/^lightblue([0-9]+)$/',
  43. 'extension' => '.png',
  44. 'shadow' => false,
  45. 'offsetImage' => '0,-19',
  46. 'offsetShadow' => false
  47. ),
  48. // openlayers std markers
  49. 'ol-marker' => array('regex' => '/^ol-marker(|-blue|-gold|-green)+$/',
  50. 'extension' => '.png',
  51. 'shadow' => '../marker_shadow.png',
  52. 'offsetImage' => '-10,-25',
  53. 'offsetShadow' => '-1,-13'
  54. ),
  55. // taken from http://www.visual-case.it/cgi-bin/vc/GMapsIcons.pl
  56. 'ylw' => array('regex' => '/^(pink|purple|red|ltblu|ylw)-pushpin$/',
  57. 'extension' => '.png',
  58. 'shadow' => '../marker_shadow.png',
  59. 'offsetImage' => '-10,-32',
  60. 'offsetShadow' => '-1,-13'
  61. ),
  62. // http://svn.openstreetmap.org/sites/other/StaticMap/symbols/0.png
  63. 'ojw' => array('regex' => '/^bullseye$/',
  64. 'extension' => '.png',
  65. 'shadow' => false,
  66. 'offsetImage' => '-20,-20',
  67. 'offsetShadow' => false
  68. )
  69. );
  70. protected $useTileCache = true;
  71. protected $tileCacheBaseDir = '../cache/tiles';
  72. protected $useMapCache = true;
  73. protected $mapCacheBaseDir = '../cache/maps';
  74. protected $mapCacheID = '';
  75. protected $mapCacheFile = '';
  76. protected $mapCacheExtension = 'png';
  77. protected $zoom, $lat, $lon, $width, $height, $markers, $image, $maptype;
  78. protected $centerX, $centerY, $offsetX, $offsetY;
  79. public function __construct()
  80. {
  81. $this->zoom = 0;
  82. $this->lat = 0;
  83. $this->lon = 0;
  84. $this->width = 500;
  85. $this->height = 350;
  86. $this->markers = array();
  87. $this->maptype = $this->tileDefaultSrc;
  88. }
  89. public function parseParams()
  90. {
  91. global $_GET;
  92. if (!empty($_GET['show'])) {
  93. $this->parseOjwParams();
  94. }
  95. else {
  96. $this->parseLiteParams();
  97. }
  98. }
  99. public function parseLiteParams()
  100. {
  101. // get zoom from GET paramter
  102. $this->zoom = $_GET['zoom'] ? intval($_GET['zoom']) : 0;
  103. if ($this->zoom > 18) $this->zoom = 18;
  104. // get lat and lon from GET paramter
  105. list($this->lat, $this->lon) = explode(',', $_GET['center']);
  106. $this->lat = floatval($this->lat);
  107. $this->lon = floatval($this->lon);
  108. // get size from GET paramter
  109. if ($_GET['size']) {
  110. list($this->width, $this->height) = explode('x', $_GET['size']);
  111. $this->width = intval($this->width);
  112. if ($this->width > $this->maxWidth) $this->width = $this->maxWidth;
  113. $this->height = intval($this->height);
  114. if ($this->height > $this->maxHeight) $this->height = $this->maxHeight;
  115. }
  116. if (!empty($_GET['markers'])) {
  117. $markers = explode('|', $_GET['markers']);
  118. foreach ($markers as $marker) {
  119. list($markerLat, $markerLon, $markerType) = explode(',', $marker);
  120. $markerLat = floatval($markerLat);
  121. $markerLon = floatval($markerLon);
  122. $markerType = basename($markerType);
  123. $this->markers[] = array('lat' => $markerLat, 'lon' => $markerLon, 'type' => $markerType);
  124. }
  125. }
  126. if ($_GET['maptype']) {
  127. if (array_key_exists($_GET['maptype'], $this->tileSrcUrl)) $this->maptype = $_GET['maptype'];
  128. }
  129. }
  130. public function parseOjwParams()
  131. {
  132. $this->lat = floatval($_GET['lat']);
  133. $this->lon = floatval($_GET['lon']);
  134. $this->zoom = intval($_GET['z']);
  135. $this->width = intval($_GET['w']);
  136. if ($this->width > $this->maxWidth) $this->width = $this->maxWidth;
  137. $this->height = intval($_GET['h']);
  138. if ($this->height > $this->maxHeight) $this->height = $this->maxHeight;
  139. if (!empty($_GET['mlat0'])) {
  140. $markerLat = floatval($_GET['mlat0']);
  141. if (!empty($_GET['mlon0'])) {
  142. $markerLon = floatval($_GET['mlon0']);
  143. $this->markers[] = array('lat' => $markerLat, 'lon' => $markerLon, 'type' => "bullseye");
  144. }
  145. }
  146. }
  147. public function lonToTile($long, $zoom)
  148. {
  149. return (($long + 180) / 360) * pow(2, $zoom);
  150. }
  151. public function latToTile($lat, $zoom)
  152. {
  153. return (1 - log(tan($lat * pi() / 180) + 1 / cos($lat * pi() / 180)) / pi()) / 2 * pow(2, $zoom);
  154. }
  155. public function initCoords()
  156. {
  157. $this->centerX = $this->lonToTile($this->lon, $this->zoom);
  158. $this->centerY = $this->latToTile($this->lat, $this->zoom);
  159. $this->offsetX = floor((floor($this->centerX) - $this->centerX) * $this->tileSize);
  160. $this->offsetY = floor((floor($this->centerY) - $this->centerY) * $this->tileSize);
  161. }
  162. public function createBaseMap()
  163. {
  164. $this->image = imagecreatetruecolor($this->width, $this->height);
  165. $startX = floor($this->centerX - ($this->width / $this->tileSize) / 2);
  166. $startY = floor($this->centerY - ($this->height / $this->tileSize) / 2);
  167. $endX = ceil($this->centerX + ($this->width / $this->tileSize) / 2);
  168. $endY = ceil($this->centerY + ($this->height / $this->tileSize) / 2);
  169. $this->offsetX = -floor(($this->centerX - floor($this->centerX)) * $this->tileSize);
  170. $this->offsetY = -floor(($this->centerY - floor($this->centerY)) * $this->tileSize);
  171. $this->offsetX += floor($this->width / 2);
  172. $this->offsetY += floor($this->height / 2);
  173. $this->offsetX += floor($startX - floor($this->centerX)) * $this->tileSize;
  174. $this->offsetY += floor($startY - floor($this->centerY)) * $this->tileSize;
  175. for ($x = $startX; $x <= $endX; $x++) {
  176. for ($y = $startY; $y <= $endY; $y++) {
  177. $url = str_replace(array('{Z}', '{X}', '{Y}'), array($this->zoom, $x, $y), $this->tileSrcUrl[$this->maptype]);
  178. $tileData = $this->fetchTile($url);
  179. if ($tileData) {
  180. $tileImage = imagecreatefromstring($tileData);
  181. } else {
  182. $tileImage = imagecreate($this->tileSize, $this->tileSize);
  183. $color = imagecolorallocate($tileImage, 255, 255, 255);
  184. @imagestring($tileImage, 1, 127, 127, 'err', $color);
  185. }
  186. $destX = ($x - $startX) * $this->tileSize + $this->offsetX;
  187. $destY = ($y - $startY) * $this->tileSize + $this->offsetY;
  188. imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
  189. }
  190. }
  191. }
  192. public function placeMarkers()
  193. {
  194. // loop thru marker array
  195. foreach ($this->markers as $marker) {
  196. // set some local variables
  197. $markerLat = $marker['lat'];
  198. $markerLon = $marker['lon'];
  199. $markerType = $marker['type'];
  200. // clear variables from previous loops
  201. $markerFilename = '';
  202. $markerShadow = '';
  203. $matches = false;
  204. // check for marker type, get settings from markerPrototypes
  205. if ($markerType) {
  206. foreach ($this->markerPrototypes as $markerPrototype) {
  207. if (preg_match($markerPrototype['regex'], $markerType, $matches)) {
  208. $markerFilename = $matches[0] . $markerPrototype['extension'];
  209. if ($markerPrototype['offsetImage']) {
  210. list($markerImageOffsetX, $markerImageOffsetY) = explode(",", $markerPrototype['offsetImage']);
  211. }
  212. $markerShadow = $markerPrototype['shadow'];
  213. if ($markerShadow) {
  214. list($markerShadowOffsetX, $markerShadowOffsetY) = explode(",", $markerPrototype['offsetShadow']);
  215. }
  216. }
  217. }
  218. }
  219. // check required files or set default
  220. if ($markerFilename == '' || !file_exists($this->markerBaseDir . '/' . $markerFilename)) {
  221. $markerIndex++;
  222. $markerFilename = 'lightblue' . $markerIndex . '.png';
  223. $markerImageOffsetX = 0;
  224. $markerImageOffsetY = -19;
  225. }
  226. // create img resource
  227. if (file_exists($this->markerBaseDir . '/' . $markerFilename)) {
  228. $markerImg = imagecreatefrompng($this->markerBaseDir . '/' . $markerFilename);
  229. } else {
  230. $markerImg = imagecreatefrompng($this->markerBaseDir . '/lightblue1.png');
  231. }
  232. // check for shadow + create shadow recource
  233. if ($markerShadow && file_exists($this->markerBaseDir . '/' . $markerShadow)) {
  234. $markerShadowImg = imagecreatefrompng($this->markerBaseDir . '/' . $markerShadow);
  235. }
  236. // calc position
  237. $destX = floor(($this->width / 2) - $this->tileSize * ($this->centerX - $this->lonToTile($markerLon, $this->zoom)));
  238. $destY = floor(($this->height / 2) - $this->tileSize * ($this->centerY - $this->latToTile($markerLat, $this->zoom)));
  239. // copy shadow on basemap
  240. if ($markerShadow && $markerShadowImg) {
  241. imagecopy($this->image, $markerShadowImg, $destX + intval($markerShadowOffsetX), $destY + intval($markerShadowOffsetY),
  242. 0, 0, imagesx($markerShadowImg), imagesy($markerShadowImg));
  243. }
  244. // copy marker on basemap above shadow
  245. imagecopy($this->image, $markerImg, $destX + intval($markerImageOffsetX), $destY + intval($markerImageOffsetY),
  246. 0, 0, imagesx($markerImg), imagesy($markerImg));
  247. };
  248. }
  249. public function tileUrlToFilename($url)
  250. {
  251. return $this->tileCacheBaseDir . "/" . str_replace(array('http://'), '', $url);
  252. }
  253. public function checkTileCache($url)
  254. {
  255. $filename = $this->tileUrlToFilename($url);
  256. if (file_exists($filename)) {
  257. return file_get_contents($filename);
  258. }
  259. }
  260. public function checkMapCache()
  261. {
  262. $this->mapCacheID = md5($this->serializeParams());
  263. $filename = $this->mapCacheIDToFilename();
  264. if (file_exists($filename)) return true;
  265. }
  266. public function serializeParams()
  267. {
  268. return join("&", array($this->zoom, $this->lat, $this->lon, $this->width, $this->height, serialize($this->markers), $this->maptype));
  269. }
  270. public function mapCacheIDToFilename()
  271. {
  272. if (!$this->mapCacheFile) {
  273. $this->mapCacheFile = $this->mapCacheBaseDir . "/" . $this->maptype . "/" . $this->zoom . "/cache_" . substr($this->mapCacheID, 0, 2) . "/" . substr($this->mapCacheID, 2, 2) . "/" . substr($this->mapCacheID, 4);
  274. }
  275. return $this->mapCacheFile . "." . $this->mapCacheExtension;
  276. }
  277. public function mkdir_recursive($pathname, $mode)
  278. {
  279. is_dir(dirname($pathname)) || $this->mkdir_recursive(dirname($pathname), $mode);
  280. return is_dir($pathname) || @mkdir($pathname, $mode);
  281. }
  282. public function writeTileToCache($url, $data)
  283. {
  284. $filename = $this->tileUrlToFilename($url);
  285. $this->mkdir_recursive(dirname($filename), 0777);
  286. file_put_contents($filename, $data);
  287. }
  288. public function fetchTile($url)
  289. {
  290. if ($this->useTileCache && ($cached = $this->checkTileCache($url))) return $cached;
  291. $ch = curl_init();
  292. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  293. curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0");
  294. curl_setopt($ch, CURLOPT_URL, $url);
  295. $tile = curl_exec($ch);
  296. curl_close($ch);
  297. if ($tile && $this->useTileCache) {
  298. $this->writeTileToCache($url, $tile);
  299. }
  300. return $tile;
  301. }
  302. public function copyrightNotice()
  303. {
  304. $logoImg = imagecreatefrompng($this->osmLogo);
  305. imagecopy($this->image, $logoImg, imagesx($this->image) - imagesx($logoImg), imagesy($this->image) - imagesy($logoImg), 0, 0, imagesx($logoImg), imagesy($logoImg));
  306. }
  307. public function sendHeader()
  308. {
  309. header('Content-Type: image/png');
  310. $expires = 60 * 60 * 24 * 14;
  311. header("Pragma: public");
  312. header("Cache-Control: maxage=" . $expires);
  313. header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT');
  314. }
  315. public function makeMap()
  316. {
  317. $this->initCoords();
  318. $this->createBaseMap();
  319. if (count($this->markers)) $this->placeMarkers();
  320. if ($this->osmLogo) $this->copyrightNotice();
  321. }
  322. public function showMap()
  323. {
  324. $this->parseParams();
  325. if ($this->useMapCache) {
  326. // use map cache, so check cache for map
  327. if (!$this->checkMapCache()) {
  328. // map is not in cache, needs to be build
  329. $this->makeMap();
  330. $this->mkdir_recursive(dirname($this->mapCacheIDToFilename()), 0777);
  331. imagepng($this->image, $this->mapCacheIDToFilename(), 9);
  332. $this->sendHeader();
  333. if (file_exists($this->mapCacheIDToFilename())) {
  334. return file_get_contents($this->mapCacheIDToFilename());
  335. } else {
  336. return imagepng($this->image);
  337. }
  338. } else {
  339. // map is in cache
  340. $this->sendHeader();
  341. return file_get_contents($this->mapCacheIDToFilename());
  342. }
  343. } else {
  344. // no cache, make map, send headers and deliver png
  345. $this->makeMap();
  346. $this->sendHeader();
  347. return imagepng($this->image);
  348. }
  349. }
  350. }
  351. $map = new staticMapLite();
  352. print $map->showMap();