function DownloadMarkup() { $resourceService = $this->site->CreateService(MgServiceType::ResourceService); $featureService = $this->site->CreateService(MgServiceType::FeatureService); $markupLayerResId = new MgResourceIdentifier($this->args['MARKUPLAYER']); $markupFsId = new MgResourceIdentifier($this->GetResourceIdPrefix() . $markupLayerResId->GetName() . '.FeatureSource'); $extension = $this->GetFileExtension($markupLayerResId->ToString()); if (strcmp($extension, ".zip") == 0) { $dataList = $resourceService->EnumerateResourceData($markupFsId); $doc = DOMDocument::LoadXML($dataList->ToString()); $dataItems = $doc->getElementsByTagName("Name"); $tmpFiles = array(); //Copy out all data files to a temp location for ($i = 0; $i < $dataItems->length; $i++) { $dataName = $dataItems->item($i)->nodeValue; $byteReader = $resourceService->GetResourceData($markupFsId, $dataName); //Sink to temp file $tmpSink = new MgByteSink($byteReader); $fileName = tempnam(sys_get_temp_dir(), $dataName); $tmpSink->ToFile($fileName); $tmpFiles[$dataName] = $fileName; } //Zip them up. $zipName = $markupLayerResId->GetName() . $extension; $zipPath = tempnam(sys_get_temp_dir(), $zipName); $zip = new ZipArchive(); $zip->open($zipPath, ZIPARCHIVE::CREATE); foreach ($tmpFiles as $dataName => $filePath) { $dataNorm = strtolower($dataName); //HACK: There must be some defect in MgFeatureService::CreateFeatureSource() for SHP //files or the FDO provider, because even if we plug in a coord sys WKT when we create //it, we get a blank prj file (both Windows/Linux). Re-uploading this same zip file back into //the widget causes problems in Linux (markup features not rendered) because of the blank prj. // //So that's the problem. Here's the workaround: If we find a blank prj file as we're assembling //the zip file for download, pump our current Map's WKT into the prj file before packaging it up // //That's what we were already doing when we called MgFeatureService::CreateFeatureSource(), but it //or the provider didn't like it. if (substr($dataNorm, strlen($dataNorm) - strlen("prj")) == "prj") { $content = file_get_contents($filePath); if (strlen($content) == 0) { $map = new MgMap(); $map->Open($resourceService, $this->args['MAPNAME']); $content = $map->GetMapSRS(); file_put_contents($filePath, $content); } } $zip->addFile($filePath, $dataName); } $zip->close(); //Serve it up for download $bs = new MgByteSource($zipPath); $br = $bs->GetReader(); $len = $br->GetLength(); $outputBuffer = ''; $buffer = ''; while ($br->Read($buffer, 50000) != 0) { $outputBuffer .= $buffer; } header("Content-Type: application/octet-stream"); header("Content-Disposition: attachment; filename={$zipName}"); header("Content-Length: " . strlen($outputBuffer)); echo $outputBuffer; //Let's be nice and clean up after ourselves unlink($zipPath); foreach ($tmpFiles as $dataName => $filePath) { unlink($filePath); } } else { //Single file, make things easier! $dataName = $markupLayerResId->GetName() . $extension; $byteReader = $resourceService->GetResourceData($markupFsId, $dataName); $len = $byteReader->GetLength(); $outputBuffer = ''; $buffer = ''; while ($byteReader->Read($buffer, 50000) != 0) { $outputBuffer .= $buffer; } header("Content-Type: " . $byteReader->GetMimeType()); header("Content-Disposition: attachment; filename={$dataName}"); header("Content-Length: " . strlen($outputBuffer)); echo $outputBuffer; } }
function ProcessLayersForLegend($mappingService, $resourceService, $map, $scale, $group, $image) { global $iconWidth, $iconHeight, $height, $fontIndex, $iconFormat, $offsetX, $offsetY, $font, $fontSizePt, $xPad, $yPad, $xIndent, $themeIcon, $groupIcon, $dwfIcon, $rasterIcon, $textColor; $layers = $map->GetLayers(); for ($i = 0; $i < $layers->GetCount(); $i++) { $layer = $layers->GetItem($i); if (!$layer->IsVisible() || !$layer->GetDisplayInLegend()) { //print_r("[SKIP]: Skipping layer (".$layer->GetLegendLabel().") - not visible or not set to display in legend <br/>"); continue; } if ($offsetY > $height) { //print_r("********** STOP! Past image bounds ****************<br/>"); break; } $parentGroup = $layer->GetGroup(); $bRequiredInLegend = false; $grpId = ""; $parentId = ""; if ($group != NULL) { $grpId = $group->GetObjectId(); } if ($parentGroup != NULL) { $parentId = $parentGroup->GetObjectId(); } if ($group == NULL && $parentGroup == NULL) { $bRequiredInLegend = true; } else { if ($parentGroup != NULL && $group != NULL && $group->GetObjectId() == $parentGroup->GetObjectId()) { $bRequiredInLegend = true; } } if (!$bRequiredInLegend) { //print_r("[SKIP]: Skipping layer (".$layer->GetLegendLabel().", group: $grpId, layer's parent: $parentId) - not required in legend <br/>"); continue; } $ldfId = $layer->GetLayerDefinition(); $layerContent = $resourceService->GetResourceContent($ldfId); $xmldoc = new DOMDocument(); $xmldoc->loadXML(ByteReaderToString($layerContent)); $scaleRanges = $xmldoc->getElementsByTagName('VectorScaleRange'); if ($scaleRanges->length == 0) { $scaleRanges = $xmldoc->getElementsByTagName('GridScaleRange'); if ($scaleRanges->length > 0) { //Draw the image imagecopy($image, $rasterIcon, $offsetX, $offsetY, 0, 0, $iconWidth, $iconHeight); //Draw the label //imagettftext($image, $fontSizePt, 0, ($offsetX + $xPad + $iconWidth), $offsetY, $textColor, $font, $layer->GetLegendLabel()); imagestring($image, $fontIndex, $offsetX + $xPad + $iconWidth, $offsetY, $layer->GetLegendLabel(), $textColor); $offsetY += $yPad; $offsetY += $iconHeight; return; } else { $scaleRanges = $xmldoc->getElementsByTagName('DrawingLayerDefinition'); if ($scaleRanges->length > 0) { //Draw the image imagecopy($image, $dwfIcon, $offsetX, $offsetY, 0, 0, $iconWidth, $iconHeight); //Draw the label //imagettftext($image, $fontSizePt, 0, ($offsetX + $xPad + $iconWidth), $offsetY, $textColor, $font, $layer->GetLegendLabel()); imagestring($image, $fontIndex, $offsetX + $xPad + $iconWidth, $offsetY, $layer->GetLegendLabel(), $textColor); $offsetY += $yPad; $offsetY += $iconHeight; return; } } } //print_r("Processing layer (".$layer->GetLegendLabel()."). Scale range count: ".$scaleRanges->length."<br/>"); for ($sc = 0; $sc < $scaleRanges->length; $sc++) { $scaleRangeObj = NULL; $scaleRangeObj->styles = array(); $scaleRange = $scaleRanges->item($sc); $minElt = $scaleRange->getElementsByTagName('MinScale'); $maxElt = $scaleRange->getElementsByTagName('MaxScale'); $minScale = "0"; $maxScale = 'infinity'; // as MDF's VectorScaleRange::MAX_MAP_SCALE if ($minElt->length > 0) { $minScale = $minElt->item(0)->nodeValue; } if ($maxElt->length > 0) { $maxScale = $maxElt->item(0)->nodeValue; } //Style is within our scale if ($minScale == "0" && $maxScale == "infinity" || $scale >= floatval($minScale) && $scale <= floatval($maxScale) || $maxScale == "infinity" && $scale >= floatval($minScale)) { $styleIndex = 0; if (!IsThemed($scaleRange, $gt)) { //print_r($layer->GetLegendLabel()." - ".$ldfId->ToString()." - Un-themed layer (geomType: $gt)<br/>"); $icon = $mappingService->GenerateLegendImage($ldfId, $scale, $iconWidth, $iconHeight, $iconFormat, $gt, 0); $sink = new MgByteSink($icon); $tempImage = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "mgo_icon" . uniqid(); $sink->ToFile($tempImage); $iconImg = imagecreatefrompng($tempImage); //Draw the image imagecopy($image, $iconImg, $offsetX, $offsetY, 0, 0, $iconWidth, $iconHeight); imagedestroy($iconImg); unlink($tempImage); //Draw the label //imagettftext($image, $fontSizePt, 0, ($offsetX + $xPad + $iconWidth), $offsetY, $textColor, $font, $layer->GetLegendLabel()); imagestring($image, $fontIndex, $offsetX + $xPad + $iconWidth, $offsetY, $layer->GetLegendLabel(), $textColor); $offsetY += $yPad; $offsetY += $iconHeight; } else { if ($scaleRange->getElementsByTagName("CompositeTypeStyle")->length > 0) { $typeStyles = array(4 => "CompositeTypeStyle"); $ruleNames = array(4 => "CompositeRule"); } else { $typeStyles = array(1 => "PointTypeStyle", 2 => "LineTypeStyle", 3 => "AreaTypeStyle"); $ruleNames = array(1 => "PointRule", 2 => "LineRule", 3 => "AreaRule"); } //print_r($layer->GetLegendLabel()." - ".$ldfId->ToString()." - Themed layer ()<br/>"); //Draw the theme icon imagecopy($image, $themeIcon, $offsetX, $offsetY, 0, 0, $iconWidth, $iconHeight); //Draw the themed layer label //imagettftext($image, $fontSizePt, 0, ($offsetX + $xPad + $iconWidth), $offsetY, $textColor, $font, $layer->GetLegendLabel()); imagestring($image, $fontIndex, $offsetX + $xPad + $iconWidth, $offsetY, $layer->GetLegendLabel(), $textColor); $offsetX += $xIndent; $offsetY += $yPad; $offsetY += $iconHeight; //for($ts=0, $count = count($typeStyles); $ts < $count; $ts++) foreach ($typeStyles as $ts => $styleName) { //print_r(" - Checking $styleName ($ts)<br/>"); $typeStyle = $scaleRange->getElementsByTagName($styleName); $catIndex = 0; //print_r(" -- Found ".$typeStyle->length." elements<br/>"); for ($st = 0; $st < $typeStyle->length; $st++) { $styleObj = NULL; // We will check if this typestyle is going to be shown in the legend $showInLegend = $typeStyle->item($st)->getElementsByTagName("ShowInLegend"); if ($showInLegend->length > 0) { if ($showInLegend->item(0)->nodeValue == "false") { //print_r("Skipping $styleName in (".$layer->GetLegendLabel().") because ShowInLegend is: ".$showInLegend->item(0)->nodeValue."<br/>"); continue; // This typestyle does not need to be shown in the legend } } $rules = $typeStyle->item($st)->getElementsByTagName($ruleNames[$ts]); for ($r = 0; $r < $rules->length; $r++) { $rule = $rules->item($r); $label = $rule->getElementsByTagName("LegendLabel"); $labelText = $label->length == 1 ? $label->item(0)->nodeValue : ""; $icon = $mappingService->GenerateLegendImage($ldfId, $scale, $iconWidth, $iconHeight, $iconFormat, $ts, $catIndex++); $sink = new MgByteSink($icon); $tempImage = sys_get_temp_dir() . DIRECTORY_SEPARATOR . "mgo_icon" . uniqid(); $sink->ToFile($tempImage); $iconImg = imagecreatefrompng($tempImage); //Draw the image imagecopy($image, $iconImg, $offsetX, $offsetY, 0, 0, $iconWidth, $iconHeight); imagedestroy($iconImg); unlink($tempImage); //Draw the label //imagettftext($image, $fontSizePt, 0, ($offsetX + $xPad + $iconWidth), $offsetY, $textColor, $font, $layer->GetLegendLabel()); imagestring($image, $fontIndex, $offsetX + $xPad + $iconWidth, $offsetY, $labelText, $textColor); $offsetY += $yPad; $offsetY += $iconHeight; if ($offsetY > $height) { break; } } } } $offsetX -= $xIndent; } } else { //print_r("[SKIP]: Layer (".$layer->GetLegendLabel()."). Current scale ($scale) does not fit scale range [$minScale to $maxScale]<br/>"); } } } }
public function GetTileXYZ($resId, $groupName, $x, $y, $z, $type, $layerNames = NULL) { $fmt = $this->ValidateRepresentation($type, array("json", "png", "png8", "jpg", "gif")); $path = self::GetTilePath($this->app, $resId, $groupName, $z, $x, $y, $type, $layerNames); clearstatcache(); $dir = dirname($path); $lockPath = "{$dir}/lock_" . $y . ".lck"; $attempts = 0; while (!@is_dir($dir)) { try { mkdir($dir, 0777, true); } catch (Exception $e) { //Another tile request may have already created this since $attempts++; //Bail after MAX_RETRY_ATTEMPTS if ($attempts >= self::MAX_RETRY_ATTEMPTS) { $this->ServerError($this->app->localizer->getText("E_FAILED_TO_CREATE_DIR_AFTER_N_ATTEMPTS", $attempts), $this->GetMimeTypeForFormat($type)); } } } //If there's a dangling lock file, attempt to remove it if (file_exists($lockPath)) { unlink($lockPath); } $fpLockFile = fopen($lockPath, "a+"); //Message of any exception caught will be set to this variable $tileError = null; $requestId = rand(); $this->app->log->debug("({$requestId}) Checking if {$path} exists"); $attempts = 0; while (!file_exists($path)) { //Bail after MAX_RETRY_ATTEMPTS if ($attempts >= self::MAX_RETRY_ATTEMPTS) { $this->ServerError($this->app->localizer->getText("E_FAILED_TO_GENERATE_TILE_AFTER_N_ATTEMPTS", $attempts), $this->GetMimeTypeForFormat($type)); } $attempts++; $this->app->log->debug("({$requestId}) {$path} does not exist. Locking for writing"); $bLocked = false; flock($fpLockFile, LOCK_EX); fwrite($fpLockFile, "."); $bLocked = true; $this->app->log->debug("({$requestId}) Acquired lock for {$path}. Checking if path exists again."); //check once more to see if the cache file was created while waiting for //the lock clearstatcache(); if (!file_exists($path)) { try { $this->app->log->debug("({$requestId}) Rendering tile to {$path}"); $bOldPath = true; if ($type != "json") { //if this is MGOS 3.0 and we're dealing with a tile set, we invoke GETTILEIMAGE as that we can pass in Tile Set Definition //resource ids without issues. We cannot create MgMaps from Tile Set Definitions that are not using the default tile provider. // //The given tile set is assumed to be using the XYZ provider, the case where the Tile Set Definition is using the default provider //is not handled if ($this->app->MG_VERSION[0] >= 3 && $resId->GetResourceType() == "TileSetDefinition") { $bOldPath = false; $sessionId = ""; if ($resId->GetRepositoryType() === MgRepositoryType::Session && $this->app->request->get("session") == null) { $sessionId = $resId->GetRepositoryName(); } $resIdStr = $resId->ToString(); $that = $this; $this->EnsureAuthenticationForHttp(function ($req, $param) use($that, $resIdStr, $groupName, $x, $y, $z, $requestId, $path) { $param->AddParameter("OPERATION", "GETTILEIMAGE"); $param->AddParameter("VERSION", "1.2.0"); $param->AddParameter("MAPDEFINITION", $resIdStr); $param->AddParameter("BASEMAPLAYERGROUPNAME", $groupName); $param->AddParameter("SCALEINDEX", $z); $param->AddParameter("TILEROW", $x); $param->AddParameter("TILECOL", $y); $that->app->log->debug("({$requestId}) Executing GETTILEIMAGE"); $that->ExecuteHttpRequest($req, function ($result, $status) use($path) { if ($status == 200) { //Need to dump the rendered tile to the specified path so the caching stuff below can still do its thing $resultObj = $result->GetResultObject(); $sink = new MgByteSink($resultObj); $sink->ToFile($path); } }); }, 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 } } //Pre MGOS 3.0 code path if ($bOldPath) { $this->app->log->debug("({$requestId}) Going down old code path"); $this->EnsureAuthenticationForSite("", true); $siteConn = new MgSiteConnection(); $siteConn->Open($this->userInfo); $map = new MgMap($siteConn); $map->Create($resId, "VectorTileMap"); $renderSvc = $siteConn->CreateService(MgServiceType::RenderingService); $groups = $map->GetLayerGroups(); $baseGroup = $groups->GetItem($groupName); //Will throw MgObjectNotFoundException -> 404 if no such group exists $factory = new MgCoordinateSystemFactory(); $mapCsWkt = $map->GetMapSRS(); $mapCs = $factory->Create($mapCsWkt); $mapExtent = $map->GetMapExtent(); $mapExLL = $mapExtent->GetLowerLeftCoordinate(); $mapExUR = $mapExtent->GetUpperRightCoordinate(); $metersPerUnit = $mapCs->ConvertCoordinateSystemUnitsToMeters(1.0); $this->app->log->debug("({$requestId}) Calc bounds from XYZ"); //XYZ to lat/lon math. From this we can convert to the bounds in the map's CS // //Source: http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames $n = pow(2, $z); $lonMin = $x / $n * 360.0 - 180.0; $latMin = rad2deg(atan(sinh(pi() * (1 - 2 * $y / $n)))); $lonMax = ($x + 1) / $n * 360.0 - 180.0; $latMax = rad2deg(atan(sinh(pi() * (1 - 2 * ($y + 1) / $n)))); $boundsMinX = min($lonMin, $lonMax); $boundsMinY = min($latMin, $latMax); $boundsMaxX = max($lonMax, $lonMin); $boundsMaxY = max($latMax, $latMin); if ($mapCs->GetCsCode() != "LL84") { $llCs = $factory->CreateFromCode("LL84"); $trans = $factory->GetTransform($llCs, $mapCs); $ul = $trans->Transform($lonMin, $latMin); $lr = $trans->Transform($lonMax, $latMax); $boundsMinX = min($lr->GetX(), $ul->GetX()); $boundsMinY = min($lr->GetY(), $ul->GetY()); $boundsMaxX = max($lr->GetX(), $ul->GetX()); $boundsMaxY = max($lr->GetY(), $ul->GetY()); } //Set all layers under group to be visible $layers = $map->GetLayers(); $layerCount = $layers->GetCount(); $groupCount = $groups->GetCount(); //Turn all groups that are not the given group to be hidden for ($i = 0; $i < $groupCount; $i++) { $group = $groups->GetItem($i); if ($group->GetName() != $groupName) { $group->SetVisible(false); } else { $group->SetVisible(true); } } for ($i = 0; $i < $layerCount; $i++) { $layer = $layers->GetItem($i); $group = $layer->GetGroup(); if (null == $group) { continue; } if ($group->GetName() != $groupName && $layer->GetLayerType() == MgLayerType::Dynamic) { $layer->SetVisible(false); continue; } if ($layer->GetLayerType() == MgLayerType::Dynamic) { $layer->SetVisible(true); } } if ($type == "json") { //error_log("($requestId) Render vector tile"); $this->PutVectorTileXYZ($map, $groupName, $siteConn, $metersPerUnit, $factory, $path, $boundsMinX, $boundsMinY, $boundsMaxX, $boundsMaxY, $layerNames); } else { $format = strtoupper($type); //error_log("($requestId) Render image tile"); $this->PutTileImageXYZ($map, $groupName, $renderSvc, $path, $format, $boundsMinX, $boundsMinY, $boundsMaxX, $boundsMaxY, $layerNames, $requestId); } } } catch (MgException $ex) { if ($bLocked) { $this->app->log->debug("({$requestId}) MgException caught " . $ex->GetDetails() . "\n" . $ex->getTraceAsString() . "\n. Releasing lock for {$path}"); $tileError = $ex->GetExceptionMessage(); flock($fpLockFile, LOCK_UN); $bLocked = false; } if ($ex instanceof MgResourceNotFoundException || $ex instanceof MgObjectNotFoundException) { $this->NotFound($ex->GetExceptionMessage(), $this->GetMimeTypeForFormat($fmt)); } else { if ($ex instanceof MgConnectionFailedException) { $this->ServiceUnavailable($ex->GetExceptionMessage(), $this->GetMimeTypeForFormat($fmt)); } } } catch (Exception $ex) { if ($bLocked) { $tileError = get_class($ex) . " - " . $ex->getMessage(); $this->app->log->debug("({$requestId}) Exception caught ({$tileError}). Releasing lock for {$path}"); flock($fpLockFile, LOCK_UN); $bLocked = false; } } } if ($bLocked) { $this->app->log->debug("({$requestId}) Releasing lock for {$path}"); flock($fpLockFile, LOCK_UN); $bLocked = false; } } //An exception occurred, try to clean up lock before bailing if ($tileError != null) { try { fclose($fpLockFile); unlink($lockPath); } catch (Exception $ex) { $this->app->log->debug("({$requestId}) Failed to delete lock file. Perhaps another concurrent request to the same tile is happening?"); } throw new Exception($tileError); } $modTime = filemtime($path); $this->app->lastModified($modTime); $this->app->log->debug("({$requestId}) Acquiring shared lock for {$path}"); //acquire shared lock for reading to prevent a problem that could occur //if a tile exists but is only partially generated. flock($fpLockFile, LOCK_SH); $this->app->log->debug("({$requestId}) Outputting {$path}"); $ext = strtoupper(pathinfo($path, PATHINFO_EXTENSION)); $mimeType = ""; switch ($ext) { case "PNG": //MgImageFormats::Png: $mimeType = MgMimeType::Png; break; case "GIF": //MgImageFormats::Gif: $mimeType = MgMimeType::Gif; break; case "JPG": //MgImageFormats::Jpeg: $mimeType = MgMimeType::Jpeg; break; case "JSON": $mimeType = MgMimeType::Json; break; } $this->app->response->header("Content-Type", $mimeType); $this->app->expires("+6 months"); $this->app->response->header("Cache-Control", "max-age=31536000, must-revalidate"); $this->app->response->setBody(file_get_contents($path)); $this->app->log->debug("({$requestId}) Releasing shared lock for {$path}"); //Release lock flock($fpLockFile, LOCK_UN); //Try to delete the lock file try { fclose($fpLockFile); unlink($lockPath); } catch (Exception $ex) { $this->app->log->debug("({$requestId}) Failed to delete lock file. Perhaps another concurrent request to the same tile is happening?"); } }