Skip to content

Commit

Permalink
proxy all vector tile requests through the server
Browse files Browse the repository at this point in the history
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
  • Loading branch information
julien-nc committed Jul 21, 2024
1 parent c30adef commit 2b77689
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 18 deletions.
7 changes: 6 additions & 1 deletion appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@

['name' => 'map#getRasterTile', 'url' => '/tiles/{service}/{x}/{y}/{z}', 'verb' => 'GET'],
['name' => 'map#nominatimSearch', 'url' => '/nominatim/search', 'verb' => 'GET'],
['name' => 'map#getMapTilerFont', 'url' => '/fonts/{fontstack}/{range}.pbf', 'verb' => 'GET'],
['name' => 'map#getMapTilerStyle', 'url' => '/maptiler/maps/{version}/style.json', 'verb' => 'GET'],
['name' => 'map#getMapTilerFont', 'url' => '/maptiler/fonts/{fontstack}/{range}.pbf', 'verb' => 'GET'],
['name' => 'map#getMapTilerTiles', 'url' => '/maptiler/tiles/{version}/tiles.json', 'verb' => 'GET'],
['name' => 'map#getMapTilerTile', 'url' => '/maptiler/tiles/{version}/{z}/{x}/{y}.{ext}', 'verb' => 'GET'],
['name' => 'map#getMapTilerSprite', 'url' => '/maptiler/maps/{version}/sprite.{ext}', 'verb' => 'GET'],
['name' => 'map#getMapTilerResource', 'url' => '/maptiler/resources/{name}', 'verb' => 'GET'],

['name' => 'page#addDirectory', 'url' => '/directories', 'verb' => 'POST'],
['name' => 'page#updateDirectory', 'url' => '/directories/{id}', 'verb' => 'PUT'],
Expand Down
121 changes: 118 additions & 3 deletions lib/Controller/MapController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataDisplayResponse;
use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\Response;
use OCP\DB\Exception;
use OCP\IRequest;
use Psr\Log\LoggerInterface;
Expand Down Expand Up @@ -58,22 +60,135 @@ public function getRasterTile(string $service, int $x, int $y, int $z, ?string $
}
}

/**
* @param string $version
* @param string|null $key
* @return Response
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function getMapTilerStyle(string $version, ?string $key = null): Response {
try {
$response = new JSONResponse($this->mapService->getMapTilerStyle($version, $key));
$response->cacheFor(60 * 60 * 24);
return $response;
} catch (Exception | Throwable $e) {
$this->logger->debug('Style not found', ['exception' => $e]);
return new JSONResponse(['exception' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}

/**
* @param string $fontstack
* @param string $range
* @param string|null $key
* @return DataDisplayResponse
* @return Response
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function getMapTilerFont(string $fontstack, string $range, ?string $key = null): DataDisplayResponse {
public function getMapTilerFont(string $fontstack, string $range, ?string $key = null): Response {
try {
$response = new DataDisplayResponse($this->mapService->getMapTilerFont($fontstack, $range, $key));
$response->cacheFor(60 * 60 * 24);
return $response;
} catch (Exception | Throwable $e) {
$this->logger->debug('Font not found', ['exception' => $e]);
return new DataDisplayResponse($e->getMessage(), Http::STATUS_NOT_FOUND);
return new JSONResponse(['exception' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}

/**
* @param string $version
* @param string|null $key
* @return JSONResponse
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function getMapTilerTiles(string $version, ?string $key = null): JSONResponse {
try {
$response = new JSONResponse($this->mapService->getMapTilerTiles($version, $key));
$response->cacheFor(60 * 60 * 24);
return $response;
} catch (Exception | Throwable $e) {
$this->logger->debug('Tiles not found', ['exception' => $e]);
return new JSONResponse(['exception' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}

/**
* @param string $version
* @param int $z
* @param int $x
* @param int $y
* @param string $ext
* @param string|null $key
* @return Response
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function getMapTilerTile(string $version, int $z, int $x, int $y, string $ext, ?string $key = null): Response {
try {
$tileResponse = $this->mapService->getMapTilerTile($version, $x, $y, $z, $ext, $key);
$response = new DataDisplayResponse(
$tileResponse['body'],
Http::STATUS_OK,
['Content-Type' => $tileResponse['headers']['Content-Type'] ?? 'image/jpeg']
);
$response->cacheFor(60 * 60 * 24);
return $response;
} catch (Exception | Throwable $e) {
$this->logger->debug('Tile not found', ['exception' => $e]);
return new JSONResponse(['exception' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}

/**
* @param string $version
* @param string $ext
* @return Response
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function getMapTilerSprite(string $version, string $ext): Response {
try {
if ($ext === 'json') {
$sprite = $this->mapService->getMapTilerSpriteJson($version);
$response = new JSONResponse($sprite);
} else {
$sprite = $this->mapService->getMapTilerSpriteImage($version, $ext);
$response = new DataDisplayResponse(
$sprite['body'],
Http::STATUS_OK,
['Content-Type' => $sprite['headers']['Content-Type'] ?? 'image/png']
);
}
$response->cacheFor(60 * 60 * 24);
return $response;
} catch (Exception | Throwable $e) {
$this->logger->debug('Sprite not found', ['exception' => $e]);
return new JSONResponse(['exception' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}

/**
* @param string $name
* @return Response
*/
#[NoAdminRequired]
#[NoCSRFRequired]
public function getMapTilerResource(string $name): Response {
try {
$resourceResponse = $this->mapService->getMapTilerResource($name);
$response = new DataDisplayResponse(
$resourceResponse['body'],
Http::STATUS_OK,
['Content-Type' => $resourceResponse['headers']['Content-Type'] ?? 'image/png']
);
$response->cacheFor(60 * 60 * 24);
return $response;
} catch (Exception | Throwable $e) {
$this->logger->debug('Resource not found', ['exception' => $e]);
return new JSONResponse(['exception' => $e->getMessage()], Http::STATUS_NOT_FOUND);
}
}

Expand Down
128 changes: 128 additions & 0 deletions lib/Service/MapService.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use OCP\Http\Client\IClient;
use OCP\Http\Client\IClientService;
use OCP\IL10N;
use OCP\IURLGenerator;
use Psr\Log\LoggerInterface;
use Throwable;

Expand All @@ -31,6 +32,7 @@ class MapService {
public function __construct(
IClientService $clientService,
private LoggerInterface $logger,
private IURLGenerator $urlGenerator,
private IL10N $l10n
) {
$this->client = $clientService->newClient();
Expand Down Expand Up @@ -136,6 +138,33 @@ public function getRasterTile(string $service, int $x, int $y, int $z, ?string $
return $body;
}

/**
* @param string $version
* @param string|null $key
* @return array
* @throws Exception
*/
public function getMapTilerStyle(string $version, ?string $key = null): array {
$url = 'https://api.maptiler.com/maps/' . $version . '/style.json';
if ($key !== null) {
$url .= '?key=' . $key;
}
$body = $this->client->get($url)->getBody();
if (is_resource($body)) {
$content = stream_get_contents($body);
} else {
$content = $body;
}
$replacementUrl = $this->urlGenerator->linkToRouteAbsolute(Application::APP_ID . '.page.index') . 'maptiler';
$style = json_decode(preg_replace('/https:\/\/api\.maptiler\.com/', $replacementUrl, $content), true);
foreach ($style['layers'] as $i => $layer) {
if (is_array($layer['layout']) && empty($layer['layout'])) {
$style['layers'][$i]['layout'] = (object)[];
}
}
return $style;
}

/**
* @param string $fontstack
* @param string $range
Expand All @@ -159,6 +188,105 @@ public function getMapTilerFont(string $fontstack, string $range, ?string $key =
return $body;
}

/**
* @param string $version
* @param string|null $key
* @return array
* @throws Exception
*/
public function getMapTilerTiles(string $version, ?string $key = null): array {
$url = 'https://api.maptiler.com/tiles/' . $version . '/tiles.json';
if ($key !== null) {
$url .= '?key=' . $key;
}
$body = $this->client->get($url)->getBody();
if (is_resource($body)) {
$content = stream_get_contents($body);
if ($content === false) {
throw new Exception('No content');
}
} else {
$content = $body;
}
$replacementUrl = $this->urlGenerator->linkToRouteAbsolute(Application::APP_ID . '.page.index') . 'maptiler';
return json_decode(preg_replace('/https:\/\/api\.maptiler\.com/', $replacementUrl, $content), true);
}

/**
* @param string $version
* @param int $x
* @param int $y
* @param int $z
* @param string $ext
* @param string|null $key
* @return array
* @throws Exception
*/
public function getMapTilerTile(string $version, int $x, int $y, int $z, string $ext, ?string $key = null): array {
$url = 'https://api.maptiler.com/tiles/' . $version . '/' . $z . '/' . $x . '/' . $y . '.' . $ext;
if ($key !== null) {
$url .= '?key=' . $key;
}
$response = $this->client->get($url);
$body = $response->getBody();
$headers = $response->getHeaders();
return [
'body' => $body,
'headers' => $headers,
];
}

/**
* @param string $version
* @return array
* @throws Exception
*/
public function getMapTilerSpriteJson(string $version): array {
$url = 'https://api.maptiler.com/maps/' . $version . '/sprite.json';
$body = $this->client->get($url)->getBody();
if (is_resource($body)) {
$content = stream_get_contents($body);
if ($content === false) {
throw new Exception('No content');
}
return json_decode($content, true);
}
return json_decode($body, true);
}

/**
* @param string $version
* @param string $ext
* @return array
* @throws Exception
*/
public function getMapTilerSpriteImage(string $version, string $ext): array {
$url = 'https://api.maptiler.com/maps/' . $version . '/sprite.' . $ext;
$response = $this->client->get($url);
$body = $response->getBody();
$headers = $response->getHeaders();
return [
'body' => $body,
'headers' => $headers,
];
}

/**
* @param string $name
* @return array
* @throws Exception
*/
public function getMapTilerResource(string $name): array {
$url = 'https://api.maptiler.com/resources/' . $name;
$response = $this->client->get($url);
$body = $response->getBody();
$headers = $response->getHeaders();
return [
'body' => $body,
'headers' => $headers,
];
}

/**
* Search items
*
Expand Down
8 changes: 5 additions & 3 deletions src/components/map/MaplibreMap.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class="map-wrapper"
:class="{ withTopLeftButton }">
<a href="https://www.maptiler.com" class="watermark">
<img src="https://api.maptiler.com/resources/logo.svg"
<img :src="logoUrl"
alt="MapTiler logo">
</a>
<div id="gpxpod-map" ref="mapContainer" />
Expand Down Expand Up @@ -84,7 +84,7 @@ import '@maplibre/maplibre-gl-geocoder/dist/maplibre-gl-geocoder.css'
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
import moment from '@nextcloud/moment'
import { imagePath } from '@nextcloud/router'
import { imagePath, generateUrl } from '@nextcloud/router'
import {
getRasterTileServers,
Expand Down Expand Up @@ -180,6 +180,7 @@ export default {
nonPersistentPopup: null,
positionMarkerEnabled: false,
positionMarkerLngLat: null,
logoUrl: generateUrl('/apps/gpxpod/maptiler/resources/logo.svg'),
}
},
Expand Down Expand Up @@ -485,7 +486,8 @@ export default {
this.map.addSource('terrain', {
type: 'raster-dem',
url: 'https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=' + this.settings.maptiler_api_key,
// url: 'https://api.maptiler.com/tiles/terrain-rgb/tiles.json?key=' + this.settings.maptiler_api_key,
url: generateUrl('/apps/gpxpod/maptiler/tiles/terrain-rgb-v2/tiles.json?key=' + this.settings.maptiler_api_key),
})
// Setting up the terrain with a 0 exaggeration factor
Expand Down
Loading

0 comments on commit 2b77689

Please sign in to comment.