/** * @param InputInterface $input * @param OutputInterface $output * * @throws \InvalidArgumentException * @return int|null|void */ protected function execute(InputInterface $input, OutputInterface $output) { $this->helper = $this->getHelper('resource'); $mapmode = strtolower($input->getOption('mapmode')); if (!in_array($mapmode, array('world', 'server'))) { throw new \InvalidArgumentException("--mapmode must be 'world' or 'server'"); } $mapname = $input->getOption('mapname'); $this->mapdir = $input->getOption('mapdir'); $lang = $input->getOption('lang'); if ($this->mapdir[0] != '/') { $this->mapdir = $this->helper->get('app.path') . '/' . $this->mapdir; } $withMap = $input->hasParameterOption('--with-map'); $withCity = $input->hasParameterOption('--with-city'); $withRegionColor = $input->hasParameterOption('--with-region-color'); $output->writeln("======================="); $output->writeln("mode = <info>{$mapmode}</info>"); $output->writeln("mapdir = <info>{$this->mapdir}</info>"); if (!$withMap && !$withCity && empty($lang)) { throw new \InvalidArgumentException("Use --with-map, --with-city, --lang options\n"); } $config = $this->helper->get('map-config'); $this->proj = new MapProjection(); $this->proj->setServerZones($this->helper->get('server.json.array')); $maps = $config['maps']; if ($mapmode == 'world') { $this->proj->setWorldZones($this->helper->get('world.json.array')); } else { // include individual zone map $maps = array_merge($maps, $config['zones']); unset($maps['world']); $this->proj->setWorldZones(array('grid' => array(array(0, 47520), array(108000, 0)))); } $this->mapmode = $mapmode; $this->mapname = $mapname; $this->tileStorage = $this->helper->get('tilestorage'); // map tiles $minMapZoom = 1; $maxMapZoom = 11; // city map on world image $minCityZoom = 10; $maxCityZoom = 11; // text tiles $minTextZoom = 5; $maxTextZoom = 12; // generate tiles for world map zone placement if ($withMap) { $this->doMaps($maps, $minMapZoom, $maxMapZoom, $output); } if ($withCity) { $this->doMaps($config['cities'], $minCityZoom, $maxCityZoom, $output); } if (!empty($lang)) { $languages = explode(',', $lang); foreach ($languages as $l) { $this->doTextTiles($l, $minTextZoom, $maxTextZoom, $output); } } }
/** * @return float */ public function getTileSize() { $size = TileStorageInterface::TILE_SIZE; if ($this->zoom > $this->maxZoom) { $size = $this->proj->scale($this->zoom) / $this->proj->scale($this->maxZoom) * $size; } return $size; }
/** * Draw same type to labels (area, region, etc) into tiles * * @param int $zoom * @param Label[] $labels * @param array $style * @param bool $withIcon * * @throws \RuntimeException */ protected function processLabels($zoom, $labels, $style, $withIcon) { $scale = $this->proj->scale($zoom); $count = count($labels); foreach ($labels as $id => $label) { $mem = memory_get_usage(); if (!isset($label->text[$this->lang])) { throw new \RuntimeException("Missing translation ({$this->lang}), abort"); } // center point in grid $point = new Point($label->point->x * $scale, $label->point->y * $scale); $text = $label->text[$this->lang]; // center tile $txHome = floor($point->x / TileStorageInterface::TILE_SIZE); $tyHome = floor($point->y / TileStorageInterface::TILE_SIZE); $this->info("[{$mem}] + ({$zoom}) ({$count}) [{$txHome}, {$tyHome}], {$text}, [K\r"); --$count; // label position relative to tile $cx = $point->x - $txHome * TileStorageInterface::TILE_SIZE; $cy = $point->y - $tyHome * TileStorageInterface::TILE_SIZE; $this->debug("+ ({$id}:{$text}) {$point} ({$txHome}, {$tyHome}) -> cx/cy [{$cx}, {$cy}]\n"); $bbox = $this->getTextDimensions($style, $text); $tileBounds = $this->getTileBounds($point, $bbox); // 1x1 tile will give 0/0 as width/height $xTiles = $tileBounds->getWidth() + 1; $yTiles = $tileBounds->getHeight() + 1; $tx1 = $tileBounds->left; $ty1 = $tileBounds->top; // work image $canvas = $this->createTile($xTiles * TileStorageInterface::TILE_SIZE, $yTiles * TileStorageInterface::TILE_SIZE); // load tiles that needs to be modified $this->loadCanvas($canvas, $zoom, $tx1, $ty1, $xTiles, $yTiles); // label position relative to canvas $p = new Point($cx + ($txHome - $tx1) * TileStorageInterface::TILE_SIZE, $cy + ($tyHome - $ty1) * TileStorageInterface::TILE_SIZE); if ($withIcon) { $this->drawIcon($canvas, $p); } if ($style['fontSize'] > 0) { $this->drawText($canvas, $p, $style, $text, $label->color); } // now save those tiles back $this->saveCanvas($canvas, $zoom, $tx1, $ty1, $xTiles, $yTiles); imagedestroy($canvas); } }
/** * @return resource * @throws \RuntimeException */ protected function _background() { $canvas = imagecreatetruecolor($this->width, $this->height); if ($canvas === false) { throw new \RuntimeException("Unable to create output image at size [{$this->width}x{$this->height}]"); } if ($this->center === null) { if ($this->bounds === null) { // no features on map, so take center point in world zone $world = $this->proj->getZoneBounds('world'); $this->bounds = clone $world; } $cx = ($this->bounds->left + $this->bounds->right) / 2; $cy = ($this->bounds->top + $this->bounds->bottom) / 2; $this->center = new Point($cx, $cy); } else { // this comes in handy with automatic zoom $this->extend($this->center); } if ($this->zoom === null) { $this->zoom = $this->getBoundsZoom(); } //calculate viewport $scale = $this->getZoomScale(); // calculate viewport $halfWidth = $this->width / 2; $halfHeight = $this->height / 2; // absolute coords for viewport $vpLeft = $this->center->x * $scale - $halfWidth; $vpTop = $this->center->y * $scale - $halfHeight; $vpRight = $vpLeft + $this->width; $vpBottom = $vpTop + $this->height; $this->viewport = new Bounds($vpLeft, $vpBottom, $vpRight, $vpTop); $this->draw($canvas); return $canvas; }
/** * @param int $zoom * @param float $expected * * @dataProvider zoomScaleProvider */ public function testScale($zoom, $expected) { $mod = $this->proj->scale($zoom); $this->assertEquals($expected, $mod); }
<?php use Bmsite\Maps\MapProjection; use Bmsite\Maps\ResourceLoader; use Bmsite\Maps\StaticMap; require_once __DIR__ . '/../vendor/autoload.php'; $tiledir = '/srv/websites/maps/htdocs/webroot/api/tiles'; $params = array('maptype' => 'atys', 'size' => '512x300', 'maxzoom' => 10, 'markers' => array('icon:lm_marker|color:0xff5050|label:Glue|8749.71,-3163.33', 'icon:lm_marker|color:0xff5050|label:Zun|8812.64,-3243.71', 'icon:lm_marker|color:0xff5050|label:Koorin|8732.69,-3387.78', 'icon:lm_marker|color:0xff5050|label:Splinter|8909.87,-3028.54', 'icon:lm_marker|color:0xff5050|label:Oath|8991.02,-3053.90')); $loader = new ResourceLoader(); $proj = new MapProjection(); $proj->setServerZones($loader->loadJson('server.json')); $proj->setWorldZones($loader->loadJson('world.json')); $map = new StaticMap($tiledir, $proj); $map->configure($params); $etag = $map->etag(); echo "etag:[{$etag}]\n"; $img = $map->render(); file_put_contents(__DIR__ . '/../static-map-result.png', $img); $size = strlen($img); echo "size:[{$size}]\n";
/** * @param resource $mapImage * @param int $zoom * @param int $mapImageWidth * @param int $mapImageHeight * @param Bounds $zoneBounds */ protected function mapCutter($mapImage, $zoom, $mapImageWidth, $mapImageHeight, $zoneBounds) { // map image coords at base zoom $zoneLeft = $zoneBounds->left; $zoneBottom = $zoneBounds->bottom; $zoneRight = $zoneBounds->right; $zoneTop = $zoneBounds->top; $this->info("[K"); $zoomScale = $this->projWorld->scale($zoom); // coords at current zoom level $left = $zoneLeft * $zoomScale; $bottom = $zoneBottom * $zoomScale; $right = $zoneRight * $zoomScale; $top = $zoneTop * $zoomScale; $width = $right - $left; $height = $bottom - $top; $scaleWidth = $mapImageWidth / $width; $scaleHeight = $mapImageHeight / $height; $this->debug("({$zoom}) map size ({$width}, {$height}), scaled (%.3f, %.3f)\n", $scaleWidth, $scaleHeight); // tiles this image lands $leftTile = floor($left / TileStorageInterface::TILE_SIZE); $topTile = floor($top / TileStorageInterface::TILE_SIZE); // right/bottom are +1 in value // (map on 0,0 tile has 1,1 as right/bottom) as we need top-left coords for that tile $rightTile = ceil($right / TileStorageInterface::TILE_SIZE); $bottomTile = ceil($bottom / TileStorageInterface::TILE_SIZE); $xTiles = $rightTile - $leftTile; $yTiles = $bottomTile - $topTile; $this->debug(" num tiles ({$xTiles}, {$yTiles}), map at ({$left}, {$top}):({$right}, {$bottom}) on tiles({$leftTile}, {$topTile}, %d, %d)\n", $rightTile - 1, $bottomTile - 1); $x1 = 0; $cw = 0; for ($xTile = 0; $xTile < $xTiles; $xTile++) { $y1 = 0; $ch = 0; for ($yTile = 0; $yTile < $yTiles; $yTile++) { $mem = memory_get_usage(); $this->info("[{$mem}] - zoom % 2d (% 5dx% 5d @ %8d grid, mul=%.5f), tile % 3dx% 3d[K\r", $zoom, $width, $height, pow(2, $zoom) * TileStorageInterface::TILE_SIZE, $zoomScale, $xTile, $yTile); // tile coords within grid $tx1 = ($leftTile + $xTile) * TileStorageInterface::TILE_SIZE; $ty1 = ($topTile + $yTile) * TileStorageInterface::TILE_SIZE; $tx2 = ($leftTile + $xTile) * TileStorageInterface::TILE_SIZE + TileStorageInterface::TILE_SIZE; $ty2 = ($topTile + $yTile) * TileStorageInterface::TILE_SIZE + TileStorageInterface::TILE_SIZE; // image padding in tile $pad_l = 0; $pad_t = 0; $pad_r = 0; $pad_b = 0; if ($tx1 < $left) { $pad_l = floor($left - $tx1); } if ($ty1 < $top) { $pad_t = floor($top - $ty1); } if ($tx2 > $right) { $pad_r = ceil($tx2 - $right); } if ($ty2 > $bottom) { $pad_b = ceil($ty2 - $bottom); } $this->debug(">> (xTile:{$xTile}, yTile:{$yTile}): (tx1:{$tx1},ty1:{$ty1}):(tx2:{$tx2},ty2:{$ty2}), padding ({$pad_l}, {$pad_t}):({$pad_r}, {$pad_b})\n"); $dstWidth = TileStorageInterface::TILE_SIZE - $pad_r - $pad_l; $dstHeight = TileStorageInterface::TILE_SIZE - $pad_b - $pad_t; $cw = $dstWidth * $scaleWidth; $ch = $dstHeight * $scaleHeight; $this->debug(">> copy from (%d, %d):(%d,%d) to tile (%d, %d):(%d,%d)\n", $x1, $y1, $cw, $ch, $pad_l, $pad_t, $dstWidth, $dstHeight); $out = $this->loadTileImage($zoom, $leftTile + $xTile, $topTile + $yTile); imagecopyresampled($out, $mapImage, $pad_l, $pad_t, $x1, $y1, $dstWidth, $dstHeight, $cw, $ch); if ($this->debug) { $c = imagecolorallocatealpha($out, 255, 0, 0, 50); imagerectangle($out, $pad_l, $pad_t, $pad_l + $dstWidth, $pad_t + $dstHeight, $c); } $this->saveTileImage($zoom, $leftTile + $xTile, $topTile + $yTile, $out); $y1 += $ch; } $x1 += $cw; } }