diff --git a/composer.json b/composer.json index 50121d7..a724fc9 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,9 @@ }, "files": [ "lib/config.php", - "lib/helpers.php" + "lib/helpers.php", + "p3k/geo/WebMercator.php", + "p3k/geo/StaticMap.php" ] } } diff --git a/composer.lock b/composer.lock index ea527d3..5597bbe 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "08543114c4fc08343de999396b54476d", + "hash": "67cc4cf59f1b61424cfb0b5d47668c2f", "packages": [ { "name": "p3k/slim-savant", diff --git a/controllers/main.php b/controllers/main.php index f4fa9af..3fe1fc8 100644 --- a/controllers/main.php +++ b/controllers/main.php @@ -12,6 +12,6 @@ $app->get('/map', function() use($app) { $app->get('/map/img', function() use($app) { $params = $app->request()->params(); $app->response['Content-type'] = 'image/png'; - $assetPath = dirname(__FILE__) . '/../p3k/timezone/images'; + $assetPath = dirname(__FILE__) . '/../p3k/map/images'; $map = p3k\geo\StaticMap\generate($params, null, $assetPath); }); diff --git a/p3k/geo/StaticMap.php b/p3k/geo/StaticMap.php new file mode 100644 index 0000000..a1c0806 --- /dev/null +++ b/p3k/geo/StaticMap.php @@ -0,0 +1,515 @@ + 90, + 'maxLat' => -90, + 'minLng' => 180, + 'maxLng' => -180 + ); + + // If any markers are specified, choose a default lat/lng as the center of all the markers + $markers = array(); + if($markersTemp=k($params,'marker')) { + if(!is_array($markersTemp)) + $markersTemp = array($markersTemp); + + // If no latitude is set, use the center of all the markers + foreach($markersTemp as $i=>$m) { + if(preg_match_all('/(?P[a-z]+):(?P[^;]+)/', $m, $matches)) { + $properties = array(); + foreach($matches['k'] as $j=>$key) { + $properties[$key] = $matches['v'][$j]; + } + + // Skip invalid marker definitions, show error in a header + if(array_key_exists('icon', $properties) && ( + (array_key_exists('lat', $properties) && array_key_exists('lng', $properties)) + || array_key_exists('location', $properties) + ) + ) { + + // Geocode the provided location and return lat/lng + if(array_key_exists('location', $properties)) { + $result = ArcGISGeocoder::geocode($properties['location']); + if(!$result->success) { + #header('X-Marker-' . ($i+1) . ': error geocoding location "' . $properties['location'] . '"'); + continue; + } + + $properties['lat'] = $result->latitude; + $properties['lng'] = $result->longitude; + } + + if(preg_match('/https?:\/\/(.+)/', $properties['icon'], $match)) { + // Looks like an external image, attempt to download it + $ch = curl_init($properties['icon']); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + $img = curl_exec($ch); + $properties['iconImg'] = @imagecreatefromstring($img); + if(!$properties['iconImg']) { + $properties['iconImg'] = false; + } + } else { + $properties['iconImg'] = imagecreatefrompng($assetPath . '/' . $properties['icon'] . '.png'); + } + + if($properties['iconImg']) { + $markers[] = $properties; + } + + if($properties['lat'] < $bounds['minLat']) + $bounds['minLat'] = $properties['lat']; + + if($properties['lat'] > $bounds['maxLat']) + $bounds['maxLat'] = $properties['lat']; + + if($properties['lng'] < $bounds['minLng']) + $bounds['minLng'] = $properties['lng']; + + if($properties['lng'] > $bounds['maxLng']) + $bounds['maxLng'] = $properties['lng']; + } else { + #header('X-Marker-' . ($i+1) . ': missing icon, or lat/lng/location parameters'); + } + } + } + } + + + $paths = array(); + if($pathsTemp=k($params,'path')) { + if(!is_array($pathsTemp)) + $pathsTemp = array($pathsTemp); + + foreach($pathsTemp as $i=>$path) { + $properties = array(); + if(preg_match_all('/(?P[a-z]+):(?P[^;]+)/', $path, $matches)) { + foreach($matches['k'] as $j=>$key) { + $properties[$key] = $matches['v'][$j]; + } + } + + // Set default color and weight if none specified + if(!array_key_exists('color', $properties)) + $properties['color'] = '333333'; + if(!array_key_exists('weight', $properties)) + $properties['weight'] = 3; + + // Now parse the points into an array + if(preg_match_all('/(?P\[[0-9\.-]+,[0-9\.-]+\])/', $path, $matches)) { + $properties['path'] = json_decode('[' . implode(',', $matches['point']) . ']'); + // Adjust the bounds to fit the path + + foreach($properties['path'] as $point) { + if($point[1] < $bounds['minLat']) + $bounds['minLat'] = $point[1]; + + if($point[1] > $bounds['maxLat']) + $bounds['maxLat'] = $point[1]; + + if($point[0] < $bounds['minLng']) + $bounds['minLng'] = $point[0]; + + if($point[0] > $bounds['maxLng']) + $bounds['maxLng'] = $point[0]; + } + } + + if(array_key_exists('path', $properties)) + $paths[] = $properties; + } + } + + + $defaultLatitude = $bounds['minLat'] + (($bounds['maxLat'] - $bounds['minLat']) / 2); + $defaultLongitude = $bounds['minLng'] + (($bounds['maxLng'] - $bounds['minLng']) / 2); + + if(k($params,'latitude') !== false) { + $latitude = k($params,'latitude'); + $longitude = k($params,'longitude'); + } elseif(k($params,'location') !== false) { + $result = ArcGISGeocoder::geocode(k($params,'location')); + if(!$result->success) { + $latitude = $defaultLatitude; + $longitude = $defaultLongitude; + #header('X-Geocode: error'); + #header('X-Geocode-Result: ' . $result->raw); + } else { + $latitude = $result->latitude; + $longitude = $result->longitude; + #header('X-Geocode: success'); + #header('X-Geocode-Result: ' . $latitude . ', ' . $longitude); + } + } else { + $latitude = $defaultLatitude; + $longitude = $defaultLongitude; + } + + + $width = k($params, 'width', 300); + $height = k($params, 'height', 300); + + + // If no zoom is specified, choose a zoom level that will fit all the markers and the path + if(k($params,'zoom')) { + $zoom = k($params,'zoom'); + } else { + + // start at max zoom level (20) + $fitZoom = 20; + $doesNotFit = true; + while($fitZoom > 1 && $doesNotFit) { + $center = webmercator\latLngToPixels($latitude, $longitude, $fitZoom); + + $leftEdge = $center['x'] - $width/2; + $topEdge = $center['y'] - $height/2; + + // check if the bounding rectangle fits within width/height + $sw = webmercator\latLngToPixels($bounds['minLat'], $bounds['minLng'], $fitZoom); + $ne = webmercator\latLngToPixels($bounds['maxLat'], $bounds['maxLng'], $fitZoom); + + $fitHeight = abs($ne['y'] - $sw['y']); + $fitWidth = abs($ne['x'] - $sw['x']); + + if($fitHeight <= $height && $fitWidth <= $width) { + $doesNotFit = false; + } + + $fitZoom--; + } + + $zoom = $fitZoom; + } + + if(k($params,'maxzoom') && k($params,'maxzoom') < $zoom) { + $zoom = k($params,'maxzoom'); + } + + + + $tileServices = array( + 'streets' => array( + 'http://services.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{Z}/{Y}/{X}' + ), + 'satellite' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}' + ), + 'hybrid' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}', + 'http://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{Z}/{Y}/{X}' + ), + 'topo' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/tile/{Z}/{Y}/{X}' + ), + 'gray' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{Z}/{Y}/{X}', + 'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Reference/MapServer/tile/{Z}/{Y}/{X}' + ), + 'gray-background' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{Z}/{Y}/{X}', + ), + 'oceans' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/Ocean_Basemap/MapServer/tile/{Z}/{Y}/{X}' + ), + 'national-geographic' => array( + 'http://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{Z}/{Y}/{X}' + ), + 'osm' => array( + 'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png' + ), + 'stamen-toner' => array( + 'http://tile.stamen.com/toner/{Z}/{X}/{Y}.png' + ), + 'stamen-toner-background' => array( + 'http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png' + ), + 'stamen-toner-lite' => array( + 'http://tile.stamen.com/toner-lite/{Z}/{X}/{Y}.png' + ), + 'stamen-terrain' => array( + 'http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png' + ), + 'stamen-terrain-background' => array( + 'http://tile.stamen.com/terrain-background/{Z}/{X}/{Y}.png' + ), + 'stamen-watercolor' => array( + 'http://tile.stamen.com/watercolor/{Z}/{X}/{Y}.png' + ) + ); + + if(k($params,'basemap') && k($tileServices, k($params,'basemap'))) { + $tileURL = $tileServices[k($params,'basemap')][0]; + if(array_key_exists(1, $tileServices[k($params,'basemap')])) + $overlayURL = $tileServices[k($params,'basemap')][1]; + else + $overlayURL = 0; + } else { + $tileURL = $tileServices['gray'][0]; + $overlayURL = false; + } + + function urlForTile($x, $y, $z, $tileURL) { + return str_replace(array( + '{X}', '{Y}', '{Z}' + ), array( + $x, $y, $z + ), $tileURL); + } + + + + + $im = imagecreatetruecolor($width, $height); + + // Find the pixel coordinate of the center of the map + $center = webmercator\latLngToPixels($latitude, $longitude, $zoom); + + $leftEdge = $center['x'] - $width/2; + $topEdge = $center['y'] - $height/2; + + $tilePos = webmercator\pixelsToTile($center['x'], $center['y']); + // print_r($tilePos); + // echo '
'; + + $pos = webmercator\positionInTile($center['x'], $center['y']); + // print_r($pos); + // echo '
'; + + // For the given number of pixels, determine how many tiles are needed in each direction + $neTile = webmercator\pixelsToTile($center['x'] + $width/2, $center['y'] + $height/2); + // print_r($neTile); + // echo '
'; + + $swTile = webmercator\pixelsToTile($center['x'] - $width/2, $center['y'] - $height/2); + // print_r($swTile); + // echo '
'; + + + // Now download all the tiles + $tiles = array(); + $overlays = array(); + $chs = array(); + $mh = curl_multi_init(); + $numTiles = 0; + + for($x = $swTile['x']; $x <= $neTile['x']; $x++) { + if(!array_key_exists("$x", $tiles)) { + $tiles["$x"] = array(); + $overlays["$x"] = array(); + $chs["$x"] = array(); + $ochs["$x"] = array(); + } + + for($y = $swTile['y']; $y <= $neTile['y']; $y++) { + $url = urlForTile($x, $y, $zoom, $tileURL); + $tiles["$x"]["$y"] = false; + $chs["$x"]["$y"] = curl_init($url); + curl_setopt($chs["$x"]["$y"], CURLOPT_RETURNTRANSFER, TRUE); + curl_multi_add_handle($mh, $chs["$x"]["$y"]); + + if($overlayURL) { + $url = urlForTile($x, $y, $zoom, $overlayURL); + $overlays["$x"]["$y"] = false; + $ochs["$x"]["$y"] = curl_init($url); + curl_setopt($ochs["$x"]["$y"], CURLOPT_RETURNTRANSFER, TRUE); + curl_multi_add_handle($mh, $ochs["$x"]["$y"]); + } + + $numTiles++; + } + } + + $running = null; + // Execute the handles. Blocks until all are finished. + do { + $mrc = curl_multi_exec($mh, $running); + } while($running > 0); + + // In case any of the tiles fail, they will be grey instead of throwing an error + $blank = imagecreatetruecolor(256, 256); + $grey = imagecolorallocate($im, 224, 224, 224); + imagefill($blank, 0,0, $grey); + + foreach($chs as $x=>$yTiles) { + foreach($yTiles as $y=>$ch) { + $content = curl_multi_getcontent($ch); + if($content) + $tiles["$x"]["$y"] = @imagecreatefromstring($content); + else + $tiles["$x"]["$y"] = $blank; + } + } + + if($overlayURL) { + foreach($ochs as $x=>$yTiles) { + foreach($yTiles as $y=>$ch) { + $content = curl_multi_getcontent($ch); + if($content) + $overlays["$x"]["$y"] = @imagecreatefromstring($content); + else + $overlays["$x"]["$y"] = $blank; + } + } + } + + // Assemble all the tiles into a new image positioned as appropriate + foreach($tiles as $x=>$yTiles) { + foreach($yTiles as $y=>$tile) { + $x = intval($x); + $y = intval($y); + + $ox = (($x - $tilePos['x']) * TILE_SIZE) - $pos['x'] + ($width/2); + $oy = (($y - $tilePos['y']) * TILE_SIZE) - $pos['y'] + ($height/2); + + imagecopy($im, $tile, $ox,$oy, 0,0, imagesx($tile),imagesy($tile)); + } + } + + if($overlayURL) { + foreach($overlays as $x=>$yTiles) { + foreach($yTiles as $y=>$tile) { + $x = intval($x); + $y = intval($y); + + $ox = (($x - $tilePos['x']) * TILE_SIZE) - $pos['x'] + ($width/2); + $oy = (($y - $tilePos['y']) * TILE_SIZE) - $pos['y'] + ($height/2); + + imagecopy($im, $tile, $ox,$oy, 0,0, imagesx($tile),imagesy($tile)); + } + } + } + + + if(count($paths)) { + // Draw the path with ImageMagick because GD sucks as anti-aliased lines + $mg = new Imagick(); + $mg->newImage($width, $height, new ImagickPixel('none')); + + $draw = new ImagickDraw(); + + $colors = array(); + foreach($paths as $path) { + + $draw->setStrokeColor(new ImagickPixel('#'.$path['color'])); + $draw->setStrokeWidth($path['weight']); + + $previous = false; + foreach($path['path'] as $point) { + if($previous) { + $from = webmercator\latLngToPixels($previous[1], $previous[0], $zoom); + $to = webmercator\latLngToPixels($point[1], $point[0], $zoom); + $draw->line($from['x'] - $leftEdge,$from['y']-$topEdge, $to['x']-$leftEdge,$to['y']-$topEdge); + } + $previous = $point; + } + } + + $mg->drawImage($draw); + $mg->setImageFormat( "png" ); + + $pathImg = imagecreatefromstring($mg); + imagecopy($im, $pathImg, 0,0, 0,0, $width,$height); + } + + + // Add markers + foreach($markers as $marker) { + // Icons that start with 'dot-' do not have a shadow + $shadow = !preg_match('/^dot-/', $marker['icon']); + + if($width < 120 || $height < 120) { + $shrinkFactor = 1.5; + } else { + $shrinkFactor = 1; + } + + // Icons with a shadow are centered at the bottom middle pixel. + // Icons with no shadow are centered in the center pixel. + + $px = webmercator\latLngToPixels($marker['lat'], $marker['lng'], $zoom); + $pos = array( + 'x' => $px['x'] - $leftEdge, + 'y' => $px['y'] - $topEdge + ); + + if($shrinkFactor > 1) { + $markerImg = imagecreatetruecolor(round(imagesx($marker['iconImg'])/$shrinkFactor), round(imagesy($marker['iconImg'])/$shrinkFactor)); + imagealphablending($markerImg, true); + $color = imagecolorallocatealpha($markerImg, 0, 0, 0, 127); + imagefill($markerImg, 0,0, $color); + imagecopyresampled($markerImg, $marker['iconImg'], 0,0, 0,0, imagesx($markerImg),imagesy($markerImg), imagesx($marker['iconImg']),imagesy($marker['iconImg'])); + } else { + $markerImg = $marker['iconImg']; + } + + if($shadow) { + $iconPos = array( + 'x' => $pos['x'] - round(imagesx($markerImg)/2), + 'y' => $pos['y'] - imagesy($markerImg) + ); + } else { + $iconPos = array( + 'x' => $pos['x'] - round(imagesx($markerImg)/2), + 'y' => $pos['y'] - round(imagesy($markerImg)/2) + ); + } + + imagecopy($im, $markerImg, $iconPos['x'],$iconPos['y'], 0,0, imagesx($markerImg),imagesy($markerImg)); + } + + + + if(k($params,'attribution') != 'none') { + $logo = imagecreatefrompng($assetPath . '/powered-by-esri.png'); + + // Shrink the esri logo if the image is small + if($width > 120) { + if($width < 220) { + $shrinkFactor = 2; + imagecopyresampled($im, $logo, $width-round(imagesx($logo)/$shrinkFactor)-4, $height-round(imagesy($logo)/$shrinkFactor)-4, 0,0, round(imagesx($logo)/$shrinkFactor),round(imagesy($logo)/$shrinkFactor), imagesx($logo),imagesy($logo)); + } else { + imagecopy($im, $logo, $width-imagesx($logo)-4, $height-imagesy($logo)-4, 0,0, imagesx($logo),imagesy($logo)); + } + } + } + + + #header('Cache-Control: max-age=' . (60*60*24*30) . ', public'); + #header('X-Tiles-Downloaded: ' . $numTiles); + + // TODO: add caching + $fmt = k($params,'format', 'png'); + switch($fmt) { + case "jpg": + case "jpeg": + header('Content-type: image/jpg'); + $quality = k($params, 'quality', 75); + imagejpeg($im, $filename, $quality); + break; + case "png": + default: + header('Content-type: image/png'); + imagepng($im, $filename); + break; + } + imagedestroy($im); + + /** + * http://msdn.microsoft.com/en-us/library/bb259689.aspx + * http://derickrethans.nl/php-mapping.html + */ +} + +function k($a, $k, $default=false) { + if(is_array($a) && array_key_exists($k, $a) && $a[$k]) + return $a[$k]; + elseif(is_object($a) && property_exists($a, $k) && $a->$k) + return $a->$k; + else + return $default; +} diff --git a/p3k/geo/WebMercator.php b/p3k/geo/WebMercator.php new file mode 100644 index 0000000..143baee --- /dev/null +++ b/p3k/geo/WebMercator.php @@ -0,0 +1,61 @@ + lngToX($longitude, $zoom), + 'y' => latToY($latitude, $zoom) + ); +} + +function xToLng($x, $zoom) { + return (($x * 360) / totalPixelsForZoomLevel($zoom)) - 180; +} + +function yToLat($y, $zoom) { + $a = pi() * (($y / totalPixelsForZoomLevel($zoom - 1)) - 1); + return -1 * (rad2deg(asin(tanh($a)))); +} + +function pixelsToLatLng($x, $y, $zoom) { + return array( + 'lat' => yToLat($y, $zoom), + 'lng' => xToLng($x, $zoom) + ); +} + +function tileToPixels($x, $y) { + return array( + 'x' => $x * TILE_SIZE, + 'y' => $y * TILE_SIZE + ); +} + +function pixelsToTile($x, $y) { + return array( + 'x' => floor($x / TILE_SIZE), + 'y' => floor($y / TILE_SIZE) + ); +} + +function positionInTile($x, $y) { + $tile = pixelsToTile($x, $y); + return array( + 'x' => round(TILE_SIZE * (($x / TILE_SIZE) - $tile['x'])), + 'y' => round(TILE_SIZE * (($y / TILE_SIZE) - $tile['y'])) + ); +} diff --git a/p3k/map/images/dot-large-blue.png b/p3k/map/images/dot-large-blue.png new file mode 100644 index 0000000..a339326 Binary files /dev/null and b/p3k/map/images/dot-large-blue.png differ diff --git a/p3k/map/images/dot-large-gray.png b/p3k/map/images/dot-large-gray.png new file mode 100644 index 0000000..0143469 Binary files /dev/null and b/p3k/map/images/dot-large-gray.png differ diff --git a/p3k/map/images/dot-large-green.png b/p3k/map/images/dot-large-green.png new file mode 100644 index 0000000..5a95ca4 Binary files /dev/null and b/p3k/map/images/dot-large-green.png differ diff --git a/p3k/map/images/dot-large-orange.png b/p3k/map/images/dot-large-orange.png new file mode 100644 index 0000000..883641e Binary files /dev/null and b/p3k/map/images/dot-large-orange.png differ diff --git a/p3k/map/images/dot-large-pink.png b/p3k/map/images/dot-large-pink.png new file mode 100644 index 0000000..f3f2cc2 Binary files /dev/null and b/p3k/map/images/dot-large-pink.png differ diff --git a/p3k/map/images/dot-large-purple.png b/p3k/map/images/dot-large-purple.png new file mode 100644 index 0000000..052a44c Binary files /dev/null and b/p3k/map/images/dot-large-purple.png differ diff --git a/p3k/map/images/dot-large-red.png b/p3k/map/images/dot-large-red.png new file mode 100644 index 0000000..09e65a1 Binary files /dev/null and b/p3k/map/images/dot-large-red.png differ diff --git a/p3k/map/images/dot-large-yellow.png b/p3k/map/images/dot-large-yellow.png new file mode 100644 index 0000000..74d1018 Binary files /dev/null and b/p3k/map/images/dot-large-yellow.png differ diff --git a/p3k/map/images/dot-small-blue.png b/p3k/map/images/dot-small-blue.png new file mode 100644 index 0000000..944642a Binary files /dev/null and b/p3k/map/images/dot-small-blue.png differ diff --git a/p3k/map/images/dot-small-gray.png b/p3k/map/images/dot-small-gray.png new file mode 100644 index 0000000..905838d Binary files /dev/null and b/p3k/map/images/dot-small-gray.png differ diff --git a/p3k/map/images/dot-small-green.png b/p3k/map/images/dot-small-green.png new file mode 100644 index 0000000..d4933de Binary files /dev/null and b/p3k/map/images/dot-small-green.png differ diff --git a/p3k/map/images/dot-small-orange.png b/p3k/map/images/dot-small-orange.png new file mode 100644 index 0000000..480b606 Binary files /dev/null and b/p3k/map/images/dot-small-orange.png differ diff --git a/p3k/map/images/dot-small-pink.png b/p3k/map/images/dot-small-pink.png new file mode 100644 index 0000000..6102fba Binary files /dev/null and b/p3k/map/images/dot-small-pink.png differ diff --git a/p3k/map/images/dot-small-purple.png b/p3k/map/images/dot-small-purple.png new file mode 100644 index 0000000..71b4a2d Binary files /dev/null and b/p3k/map/images/dot-small-purple.png differ diff --git a/p3k/map/images/dot-small-red.png b/p3k/map/images/dot-small-red.png new file mode 100644 index 0000000..382f93a Binary files /dev/null and b/p3k/map/images/dot-small-red.png differ diff --git a/p3k/map/images/dot-small-yellow.png b/p3k/map/images/dot-small-yellow.png new file mode 100644 index 0000000..4ad376e Binary files /dev/null and b/p3k/map/images/dot-small-yellow.png differ diff --git a/p3k/map/images/fb.png b/p3k/map/images/fb.png new file mode 100644 index 0000000..286a426 Binary files /dev/null and b/p3k/map/images/fb.png differ diff --git a/p3k/map/images/google.png b/p3k/map/images/google.png new file mode 100644 index 0000000..fc9a819 Binary files /dev/null and b/p3k/map/images/google.png differ diff --git a/p3k/map/images/large-blue-blank.png b/p3k/map/images/large-blue-blank.png new file mode 100644 index 0000000..323a0f7 Binary files /dev/null and b/p3k/map/images/large-blue-blank.png differ diff --git a/p3k/map/images/large-blue-cutout.png b/p3k/map/images/large-blue-cutout.png new file mode 100644 index 0000000..7fb87a2 Binary files /dev/null and b/p3k/map/images/large-blue-cutout.png differ diff --git a/p3k/map/images/large-gray-blank.png b/p3k/map/images/large-gray-blank.png new file mode 100644 index 0000000..c255eed Binary files /dev/null and b/p3k/map/images/large-gray-blank.png differ diff --git a/p3k/map/images/large-gray-cutout.png b/p3k/map/images/large-gray-cutout.png new file mode 100644 index 0000000..4b7a5a5 Binary files /dev/null and b/p3k/map/images/large-gray-cutout.png differ diff --git a/p3k/map/images/large-gray-user.png b/p3k/map/images/large-gray-user.png new file mode 100644 index 0000000..8ecec02 Binary files /dev/null and b/p3k/map/images/large-gray-user.png differ diff --git a/p3k/map/images/large-green-blank.png b/p3k/map/images/large-green-blank.png new file mode 100644 index 0000000..f823e26 Binary files /dev/null and b/p3k/map/images/large-green-blank.png differ diff --git a/p3k/map/images/large-green-cutout.png b/p3k/map/images/large-green-cutout.png new file mode 100644 index 0000000..e13c2fd Binary files /dev/null and b/p3k/map/images/large-green-cutout.png differ diff --git a/p3k/map/images/large-orange-blank.png b/p3k/map/images/large-orange-blank.png new file mode 100644 index 0000000..77e15da Binary files /dev/null and b/p3k/map/images/large-orange-blank.png differ diff --git a/p3k/map/images/large-orange-cutout.png b/p3k/map/images/large-orange-cutout.png new file mode 100644 index 0000000..4828867 Binary files /dev/null and b/p3k/map/images/large-orange-cutout.png differ diff --git a/p3k/map/images/large-pink-blank.png b/p3k/map/images/large-pink-blank.png new file mode 100644 index 0000000..d8139f0 Binary files /dev/null and b/p3k/map/images/large-pink-blank.png differ diff --git a/p3k/map/images/large-pink-cutout.png b/p3k/map/images/large-pink-cutout.png new file mode 100644 index 0000000..b7f1df8 Binary files /dev/null and b/p3k/map/images/large-pink-cutout.png differ diff --git a/p3k/map/images/large-purple-blank.png b/p3k/map/images/large-purple-blank.png new file mode 100644 index 0000000..fdeaecc Binary files /dev/null and b/p3k/map/images/large-purple-blank.png differ diff --git a/p3k/map/images/large-purple-cutout.png b/p3k/map/images/large-purple-cutout.png new file mode 100644 index 0000000..8daaa37 Binary files /dev/null and b/p3k/map/images/large-purple-cutout.png differ diff --git a/p3k/map/images/large-red-blank.png b/p3k/map/images/large-red-blank.png new file mode 100644 index 0000000..c4ffd12 Binary files /dev/null and b/p3k/map/images/large-red-blank.png differ diff --git a/p3k/map/images/large-red-cutout.png b/p3k/map/images/large-red-cutout.png new file mode 100644 index 0000000..5617c44 Binary files /dev/null and b/p3k/map/images/large-red-cutout.png differ diff --git a/p3k/map/images/large-yellow-blank.png b/p3k/map/images/large-yellow-blank.png new file mode 100644 index 0000000..0b4724a Binary files /dev/null and b/p3k/map/images/large-yellow-blank.png differ diff --git a/p3k/map/images/large-yellow-cutout.png b/p3k/map/images/large-yellow-cutout.png new file mode 100644 index 0000000..583c970 Binary files /dev/null and b/p3k/map/images/large-yellow-cutout.png differ diff --git a/p3k/map/images/large-yellow-message.png b/p3k/map/images/large-yellow-message.png new file mode 100644 index 0000000..a7723d4 Binary files /dev/null and b/p3k/map/images/large-yellow-message.png differ diff --git a/p3k/map/images/large-yellow-user.png b/p3k/map/images/large-yellow-user.png new file mode 100644 index 0000000..bf7d6e0 Binary files /dev/null and b/p3k/map/images/large-yellow-user.png differ diff --git a/p3k/map/images/powered-by-esri.png b/p3k/map/images/powered-by-esri.png new file mode 100644 index 0000000..caeb112 Binary files /dev/null and b/p3k/map/images/powered-by-esri.png differ diff --git a/p3k/map/images/powered-by-esri.psd b/p3k/map/images/powered-by-esri.psd new file mode 100644 index 0000000..e049688 Binary files /dev/null and b/p3k/map/images/powered-by-esri.psd differ diff --git a/p3k/map/images/small-blue-blank.png b/p3k/map/images/small-blue-blank.png new file mode 100644 index 0000000..c628032 Binary files /dev/null and b/p3k/map/images/small-blue-blank.png differ diff --git a/p3k/map/images/small-blue-cutout.png b/p3k/map/images/small-blue-cutout.png new file mode 100644 index 0000000..38b5a87 Binary files /dev/null and b/p3k/map/images/small-blue-cutout.png differ diff --git a/p3k/map/images/small-gray-blank.png b/p3k/map/images/small-gray-blank.png new file mode 100644 index 0000000..56e4032 Binary files /dev/null and b/p3k/map/images/small-gray-blank.png differ diff --git a/p3k/map/images/small-gray-cutout.png b/p3k/map/images/small-gray-cutout.png new file mode 100644 index 0000000..684f648 Binary files /dev/null and b/p3k/map/images/small-gray-cutout.png differ diff --git a/p3k/map/images/small-gray-message.png b/p3k/map/images/small-gray-message.png new file mode 100644 index 0000000..6804e98 Binary files /dev/null and b/p3k/map/images/small-gray-message.png differ diff --git a/p3k/map/images/small-gray-user.png b/p3k/map/images/small-gray-user.png new file mode 100644 index 0000000..1e90b99 Binary files /dev/null and b/p3k/map/images/small-gray-user.png differ diff --git a/p3k/map/images/small-green-blank.png b/p3k/map/images/small-green-blank.png new file mode 100644 index 0000000..252ef17 Binary files /dev/null and b/p3k/map/images/small-green-blank.png differ diff --git a/p3k/map/images/small-green-cutout.png b/p3k/map/images/small-green-cutout.png new file mode 100644 index 0000000..9933e08 Binary files /dev/null and b/p3k/map/images/small-green-cutout.png differ diff --git a/p3k/map/images/small-green-user.png b/p3k/map/images/small-green-user.png new file mode 100644 index 0000000..df2f24e Binary files /dev/null and b/p3k/map/images/small-green-user.png differ diff --git a/p3k/map/images/small-orange-blank.png b/p3k/map/images/small-orange-blank.png new file mode 100644 index 0000000..2292192 Binary files /dev/null and b/p3k/map/images/small-orange-blank.png differ diff --git a/p3k/map/images/small-orange-cutout.png b/p3k/map/images/small-orange-cutout.png new file mode 100644 index 0000000..8e8ae58 Binary files /dev/null and b/p3k/map/images/small-orange-cutout.png differ diff --git a/p3k/map/images/small-pink-blank.png b/p3k/map/images/small-pink-blank.png new file mode 100644 index 0000000..9962b5d Binary files /dev/null and b/p3k/map/images/small-pink-blank.png differ diff --git a/p3k/map/images/small-pink-cutout.png b/p3k/map/images/small-pink-cutout.png new file mode 100644 index 0000000..3d742d3 Binary files /dev/null and b/p3k/map/images/small-pink-cutout.png differ diff --git a/p3k/map/images/small-pink-user.png b/p3k/map/images/small-pink-user.png new file mode 100644 index 0000000..cc2d7e7 Binary files /dev/null and b/p3k/map/images/small-pink-user.png differ diff --git a/p3k/map/images/small-purple-blank.png b/p3k/map/images/small-purple-blank.png new file mode 100644 index 0000000..b94f385 Binary files /dev/null and b/p3k/map/images/small-purple-blank.png differ diff --git a/p3k/map/images/small-purple-cutout.png b/p3k/map/images/small-purple-cutout.png new file mode 100644 index 0000000..15ace8b Binary files /dev/null and b/p3k/map/images/small-purple-cutout.png differ diff --git a/p3k/map/images/small-red-blank.png b/p3k/map/images/small-red-blank.png new file mode 100644 index 0000000..bfa8890 Binary files /dev/null and b/p3k/map/images/small-red-blank.png differ diff --git a/p3k/map/images/small-red-cutout.png b/p3k/map/images/small-red-cutout.png new file mode 100644 index 0000000..fce4e06 Binary files /dev/null and b/p3k/map/images/small-red-cutout.png differ diff --git a/p3k/map/images/small-yellow-blank.png b/p3k/map/images/small-yellow-blank.png new file mode 100644 index 0000000..45c3fcd Binary files /dev/null and b/p3k/map/images/small-yellow-blank.png differ diff --git a/p3k/map/images/small-yellow-cutout.png b/p3k/map/images/small-yellow-cutout.png new file mode 100644 index 0000000..176dc20 Binary files /dev/null and b/p3k/map/images/small-yellow-cutout.png differ diff --git a/p3k/map/images/small-yellow-user.png b/p3k/map/images/small-yellow-user.png new file mode 100644 index 0000000..e686ad3 Binary files /dev/null and b/p3k/map/images/small-yellow-user.png differ diff --git a/public/assets/sample-map.png b/public/assets/sample-map.png new file mode 100644 index 0000000..b7cb250 Binary files /dev/null and b/public/assets/sample-map.png differ diff --git a/views/layout.php b/views/layout.php index 4a73e81..872de08 100644 --- a/views/layout.php +++ b/views/layout.php @@ -4,15 +4,23 @@ Atlas - + +
+

Atlas

+

Atlas is a set of APIs for looking up information about locations.

+
+ fetch($this->page . '.php') ?> diff --git a/views/map.php b/views/map.php new file mode 100644 index 0000000..e69de29