diff --git a/.gitignore b/.gitignore index 89c611f1..68b51cb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /web.config /templates_c +/cache/vectortile +/cache/templates_c +!/cache/readme.txt \ No newline at end of file diff --git a/app/adapters/templateadapter.php b/app/adapters/templateadapter.php index 3ada3065..80248779 100644 --- a/app/adapters/templateadapter.php +++ b/app/adapters/templateadapter.php @@ -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 = ""; diff --git a/app/config.php b/app/config.php index b8899e6c..4cd8d02a 100644 --- a/app/config.php +++ b/app/config.php @@ -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" ); diff --git a/app/controller/tileservicecontroller.php b/app/controller/tileservicecontroller.php index 966a8403..6ede2db5 100644 --- a/app/controller/tileservicecontroller.php +++ b/app/controller/tileservicecontroller.php @@ -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; @@ -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); + } } ?> \ No newline at end of file diff --git a/app/routes/routes.library.php b/app/routes/routes.library.php index d9de05cc..d7f1ef47 100644 --- a/app/routes/routes.library.php +++ b/app/routes/routes.library.php @@ -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) { diff --git a/app/util/utils.php b/app/util/utils.php index 6a664d39..1b90d69f 100644 --- a/app/util/utils.php +++ b/app/util/utils.php @@ -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(); diff --git a/cache/readme.txt b/cache/readme.txt new file mode 100644 index 00000000..d7608568 --- /dev/null +++ b/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 \ No newline at end of file