Skip to content

Commit

Permalink
- Add experimental code for generating GeoJSON vector tiles. The rele…
Browse files Browse the repository at this point in the history
…vant routes and code paths are currently disabled since no mapping library out there right now is able to consume such tiles, probably due to the scale/row/col scheme that MapGuide uses which I figure is incompatible with XYZ grids that most mapping libraries are expecting. When we figure out how to ensure the right tiles are being fetched, we'll re-enable thus stuff.

- Add a new /cache subdirectory where compiled smarty templates and other assorted cache-able items will reside. Compiled smarty templates will now reside under /cache/templates_c instead of /templates_c
  • Loading branch information
jumpinjackie committed Feb 5, 2014
1 parent 9374d4d commit 78926ff
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .gitignore
@@ -1,2 +1,5 @@
/web.config
/templates_c
/cache/vectortile
/cache/templates_c
!/cache/readme.txt
1 change: 1 addition & 0 deletions app/adapters/templateadapter.php
Expand Up @@ -336,6 +336,7 @@ public function HandleGet($single) {
$reader = null;
$related = new MgRelatedFeaturesSet();
$smarty = new Smarty();
$smarty->setCompileDir($this->app->config("Cache.RootDir")."/templates_c");
//$smarty->setCaching(false);
try {
$output = "";
Expand Down
1 change: 1 addition & 0 deletions app/config.php
Expand Up @@ -21,6 +21,7 @@
"GeoRest.ConfigPath" => "./conf/data",
"MapGuide.PhysicalTilePath" => "C:/Program Files/OSGeo/MapGuide/Server/Repositories/TileCache",
"MapGuide.TileImageFormat" => "png",
"Cache.RootDir" => "./cache",
"Locale" => "en"
);

Expand Down
182 changes: 181 additions & 1 deletion app/controller/tileservicecontroller.php
Expand Up @@ -72,7 +72,7 @@ public function GetTileModificationDate($mapDefIdStr, $groupName, $scaleIndex, $
}
}

public function GetTile($resId, $groupName, $scaleIndex, $row, $col) {
private function GetTileImage($resId, $groupName, $scaleIndex, $row, $col) {
$resIdStr = $resId->ToString();
$that = $this;
$app = $this->app;
Expand Down Expand Up @@ -104,6 +104,186 @@ public function GetTile($resId, $groupName, $scaleIndex, $row, $col) {
$app->expires("+6 months");
}, true, "", $sessionId); //Tile access can be anonymous, so allow for it if credentials/session specified, but if this is a session-based Map Definition, use the session id as the nominated one
}

private static function GetTransform($featSvc, $featureSourceId, $clsDef, $targetWkt, $factory) {
$transform = null;
//Has a designated geometry property, use it's spatial context
if ($clsDef->GetDefaultGeometryPropertyName() !== "") {
$props = $clsDef->GetProperties();
$idx = $props->IndexOf($clsDef->GetDefaultGeometryPropertyName());
if ($idx >= 0) {
$geomProp = $props->GetItem($idx);
$scName = $geomProp->GetSpatialContextAssociation();
$scReader = $featSvc->GetSpatialContexts($featureSourceId, false);
while ($scReader->ReadNext()) {
if ($scReader->GetName() === $scName) {
if ($scReader->GetCoordinateSystemWkt() !== $targetWkt) {
$targetCs = $factory->Create($targetWkt);
$sourceCs = $factory->Create($scReader->GetCoordinateSystemWkt());
$transform = $factory->GetTransform($sourceCs, $targetCs);
break;
}
}
}
$scReader->Close();
}
}
return $transform;
}

private static function IsLayerVisibleAtScale($layer, $resSvc, $scale) {
$ldfId = $layer->GetLayerDefinition();
$layerContent = $resSvc->GetResourceContent($ldfId);
$doc = new DOMDocument();
$doc->loadXML($layerContent->ToString());
$scaleRangeNodes = $doc->getElementsByTagName("VectorScaleRange");
for ($i = 0; $i < $scaleRangeNodes->length; $i++) {
$scaleRangeNode = $scaleRangeNodes->item($i);

$minEl = $scaleRangeNode->getElementsByTagName("MinScale");
$maxEl = $scaleRangeNode->getElementsByTagName("MaxScale");

$minScale = 0;
$maxScale = 1000000000000.0; //MdfModel::ScaleRange::MAX_MAP_SCALE
if ($minEl->length > 0)
$minScale = floatval($minEl->item(0)->nodeValue);
if ($maxEl->length > 0)
$maxScale = floatval($maxEl->item(0)->nodeValue);

if ($scale >= $minScale && $scale < $maxScale)
return true;
}
return false;
}

private static function GetVectorTilePath($app, $resId, $groupName, $scaleIndex, $row, $col) {
$relPath = "/".$resId->GetPath()."/".$resId->GetName()."/$groupName/$scaleIndex/$row/$col.json";
$path = $app->config("AppRootDir")."/".$app->config("Cache.RootDir")."/vectortile".$relPath;
return $path;
}

private function GetGeoJsonVectorTile($resId, $groupName, $scaleIndex, $row, $col) {

$path = self::GetVectorTilePath($this->app, $resId, $groupName, $scaleIndex, $row, $col);
if (file_exists($path)) {
$this->app->lastModified(filemtime($path));
}

$this->EnsureAuthenticationForSite("", true);
$siteConn = new MgSiteConnection();
$siteConn->Open($this->userInfo);

$resSvc = $siteConn->CreateService(MgServiceType::ResourceService);
$featSvc = $siteConn->CreateService(MgServiceType::FeatureService);
$tileSvc = $siteConn->CreateService(MgServiceType::TileService);
$map = new MgMap($siteConn);
$map->Create($resId, "VectorTileMap");

$scale = $map->GetFiniteDisplayScaleAt($scaleIndex);
$layerGroups = $map->GetLayerGroups();
$baseGroup = $layerGroups->GetItem($groupName);

$factory = new MgCoordinateSystemFactory();
$mapCsWkt = $map->GetMapSRS();
$mapCs = $factory->Create($mapCsWkt);

$mapExtent = $map->GetMapExtent();
$mapExLL = $mapExtent->GetLowerLeftCoordinate();
$mapExUR = $mapExtent->GetUpperRightCoordinate();

$metersPerUnit = $mapCs->ConvertCoordinateSystemUnitsToMeters(1.0);
$metersPerPixel = 0.0254 / $map->GetDisplayDpi();

$tileWidthMCS = $tileSvc->GetDefaultTileSizeX() * $metersPerPixel * $scale / $metersPerUnit;
$tileHeightMCS = $tileSvc->GetDefaultTileSizeY() * $metersPerPixel * $scale / $metersPerUnit;

$tileMinX = $mapExLL->GetX() + ($col * $tileWidthMCS); //left edge
$tileMaxX = $mapExLL->GetX() + (($col + 1) * $tileWidthMCS); //right edge
$tileMinY = $mapExUR->GetY() - (($row + 1) * $tileHeightMCS); //bottom edge
$tileMaxY = $mapExUR->GetY() - ($row * $tileHeightMCS); //top edge

$wktRw = new MgWktReaderWriter();
$agfRw = new MgAgfReaderWriter();

$layers = $map->GetLayers();
$layerCount = $layers->GetCount();
$firstFeature = true;

$dir = dirname($path);
if (!is_dir($dir))
mkdir($dir, 0777, true);
$fp = fopen($path, "w");

$this->app->response->header("Content-Type", MgMimeType::Json);
//$this->app->response->write('{ "Type": "FeatureCollection", "features": [');
fwrite($fp, '{ "type": "FeatureCollection", "features": [');
for ($i = 0; $i < $layerCount; $i++) {
$layer = $layers->GetItem($i);
if (!self::IsLayerVisibleAtScale($layer, $resSvc, $scale))
continue;
$parentGroup = $layer->GetGroup();
if ($parentGroup != null && $parentGroup->GetObjectId() == $baseGroup->GetObjectId()) {
$wktPoly = MgUtils::MakeWktPolygon(
$tileMinX, $tileMinY, $tileMaxX, $tileMaxY);

$geom = $wktRw->Read($wktPoly);
$clsDef = $layer->GetClassDefinition();
$fsId = new MgResourceIdentifier($layer->GetFeatureSourceId());

//Set up forward and inverse transforms. Inverse for transforming map bounding box
//Forward for transforming source geometries to map space
$xform = self::GetTransform($featSvc, $fsId, $clsDef, $mapCsWkt, $factory);
$query = new MgFeatureQueryOptions();
$geomName = $layer->GetFeatureGeometryName();
if ($xform != null) {
$sourceCs = $xform->GetSource();
$targetCs = $xform->GetTarget();
$invXform = $factory->GetTransform($targetCs, $sourceCs);
$txgeom = $geom->Transform($invXform);
$query->SetSpatialFilter($geomName, $txgeom, MgFeatureSpatialOperations::EnvelopeIntersects);
} else {
$query->SetSpatialFilter($geomName, $geom, MgFeatureSpatialOperations::EnvelopeIntersects);
}

$reader = $layer->SelectFeatures($query);
$read = 0;
while ($reader->ReadNext()) {
$read++;
if (!$reader->IsNull($geomName)) {
if (!$firstFeature) {
//$this->app->response->write(",");
fwrite($fp, ",");
}
try {
$agf = $reader->GetGeometry($geomName);
$fgeom = $agfRw->Read($agf, $xform);
$geomJson = MgGeoJsonWriter::ToGeoJson($fgeom);
//$this->app->response->write('{ "Type": "Feature", "_layer": "'.$layer->GetName().'", '.$geomJson.'}');
fwrite($fp, '{ "type": "Feature", "_layer": "'.$layer->GetName().'", '.$geomJson.'}');
$firstFeature = false;
} catch (MgException $ex) {

}
}
}
$reader->Close();
}
}
//$this->app->response->write(']}');
fwrite($fp, ']}');
fclose($fp);

$this->app->lastModified(filemtime($path));
$this->app->response->setBody(file_get_contents($path));
}

public function GetTile($resId, $groupName, $scaleIndex, $row, $col, $format) {
$fmt = $this->ValidateRepresentation($format, array("img")); //, "json"));
if ($fmt === "img")
$this->GetTileImage($resId, $groupName, $scaleIndex, $row, $col, $format);
else if ($fmt === "json")
$this->GetGeoJsonVectorTile($resId, $groupName, $scaleIndex, $row, $col, $format);
}
}

?>
7 changes: 6 additions & 1 deletion app/routes/routes.library.php
Expand Up @@ -261,7 +261,12 @@
$app->get("/library/:resourcePath+/tile/:groupName/:scaleIndex/:col/:row", function($resourcePath, $groupName, $scaleIndex, $col, $row) use ($app) {
$resId = MgUtils::ParseLibraryResourceID($resourcePath);
$ctrl = new MgTileServiceController($app);
$ctrl->GetTile($resId, $groupName, $scaleIndex, $col, $row);
$ctrl->GetTile($resId, $groupName, $scaleIndex, $col, $row, "img");
});
$app->get("/library/:resourcePath+/tile.:format/:groupName/:scaleIndex/:col/:row", function($resourcePath, $format, $groupName, $scaleIndex, $col, $row) use ($app) {
$resId = MgUtils::ParseLibraryResourceID($resourcePath);
$ctrl = new MgTileServiceController($app);
$ctrl->GetTile($resId, $groupName, $scaleIndex, $col, $row, $format);
});
// Mapping Service APIs
$app->get("/library/:resourcePath+.LayerDefinition/legend/:scale/:geomtype/:themecat/icon.:format", function($resourcePath, $scale, $geomtype, $themecat, $format) use ($app) {
Expand Down
2 changes: 1 addition & 1 deletion app/util/utils.php
Expand Up @@ -388,7 +388,7 @@ public static function ParseSingleFeatureXml($classDef, $xml, $featureNodeName =

return MgUtils::ParseSingleFeatureDocument($classDef, $doc, $featureNodeName, $propertyNodeName);
}

public static function GetTransform($featSvc, $resId, $schemaName, $className, $transformto) {
$transform = null;
$factory = new MgCoordinateSystemFactory();
Expand Down
5 changes: 5 additions & 0 deletions cache/readme.txt
@@ -0,0 +1,5 @@
This directory is where mapguide-rest will store compiled smarty templates and other assorted cacheable items.

Make sure PHP and your web server have sufficient write permissions for this directory and any subfolders within

See this link for more information: http://www.smarty.net/docsv2/en/installing.smarty.basic.tpl

0 comments on commit 78926ff

Please sign in to comment.