Base map
ExpTech serves three base-map tile sets. Each has a TileJSON, so you can use it directly as a MapLibre GL JS source:
| Tiles | Format | Contents |
|---|---|---|
| Base map | Vector (.pbf) |
World land, boundaries of Taiwan counties, cities, townships, towns, and districts, and coastal segments for tsunami warnings |
| Terrain | Raster (.png, Terrain-RGB) |
Taiwan elevation, for hillshade or 3D terrain |
| Streets and buildings | Vector (.pbf, OpenMapTiles schema) |
Taiwan roads, buildings, water, place names, points of interest, and more, from OpenStreetMap |
These three tile sets do not change with time. They do not need a time list.
Data license
Section titled “Data license”- Base map, TerrainAttribution · Commercial use OK
AttributionData: ExpTech Studio (exptech.dev)
- Streets & buildingsAttribution · Commercial use OK
AttributionData: © OpenStreetMap contributors, © OpenMapTiles, ExpTech Studio (exptech.dev)
- OpenStreetMap: OpenStreetMap data is licensed under the ODbL: show "© OpenStreetMap contributors" on the map and link to https://www.openstreetmap.org/copyright .
- OpenMapTiles: Tiles built on the OpenMapTiles schema must show "© OpenMapTiles".
Where to show it, examples and commercial-use requests: Data licensing and attribution.
Authentication
Section titled “Authentication”No API Token is required. Responses include Access-Control-Allow-Origin: *, so a browser can load them cross-origin.
Base map
Section titled “Base map”curl "https://lb.exptech.dev/api/v1/map/tiles/tiles.json"const res = await fetch("https://lb.exptech.dev/api/v1/map/tiles/tiles.json");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get("https://lb.exptech.dev/api/v1/map/tiles/tiles.json", timeout=10)res.raise_for_status()print(res.json())TileJSON 2.0.0 (excerpt)
{ "tilejson": "2.0.0", "name": "ExpTech Studio Map", "format": "pbf", "minzoom": 0, "maxzoom": 12, "bounds": [ -180, -85.051129, 180, 83.634101 ], "attribution": "ExpTech Studio Map", "tiles": [ "https://lb.exptech.dev/api/v1/map/tiles/{z}/{x}/{y}.pbf" ]}Try it
zZoom level. The range is minzoom / maxzoom in the TileJSON.
xXYZ tile column.
yXYZ tile row (north to south).
curl -o output.pbf --compressed "https://lb.exptech.dev/api/v1/map/tiles/<z>/<x>/<y>.pbf"const res = await fetch("https://lb.exptech.dev/api/v1/map/tiles/<z>/<x>/<y>.pbf");if (!res.ok) throw new Error(`HTTP ${res.status}`);const blob = await res.blob();import requests
res = requests.get( "https://lb.exptech.dev/api/v1/map/tiles/<z>/<x>/<y>.pbf", timeout=10,)res.raise_for_status()with open("output.pbf", "wb") as f: f.write(res.content)Mapbox Vector Tile (Content-Type: application/octet-stream). Some tiles are gzip-compressed (Content-Encoding: gzip)binary MVT
No tile at this position (no content)No response body
Try it
Layers (source-layer)
Section titled “Layers (source-layer)”| source-layer | Geometry | Contents |
|---|---|---|
global |
Polygon | World land |
city |
Polygon | Taiwan counties and cities |
town |
Polygon | Taiwan townships, towns, and districts |
tsunami |
Line | Coastal segments for tsunami warnings |
Property fields for each layer have no published schema and may change.
Terrain
Section titled “Terrain”curl "https://lb.exptech.dev/api/v1/map/terrain/terrain.json"const res = await fetch("https://lb.exptech.dev/api/v1/map/terrain/terrain.json");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get("https://lb.exptech.dev/api/v1/map/terrain/terrain.json", timeout=10)res.raise_for_status()print(res.json())TileJSON 2.0.0 (excerpt)
{ "tilejson": "2.0.0", "name": "ExpTech Studio Terrain", "format": "png", "encoding": "mapbox", "minzoom": 0, "maxzoom": 12, "bounds": [ 120.0166, 21.8939, 122.0096, 25.3015 ], "attribution": "ExpTech Studio Map", "tiles": [ "https://lb.exptech.dev/api/v1/map/terrain/{z}/{x}/{y}.png" ]}Try it
zZoom level. The range is minzoom / maxzoom in the TileJSON.
xXYZ tile column.
yXYZ tile row (north to south).
curl -o output.png "https://lb.exptech.dev/api/v1/map/terrain/<z>/<x>/<y>.png"const res = await fetch("https://lb.exptech.dev/api/v1/map/terrain/<z>/<x>/<y>.png");if (!res.ok) throw new Error(`HTTP ${res.status}`);const blob = await res.blob();import requests
res = requests.get( "https://lb.exptech.dev/api/v1/map/terrain/<z>/<x>/<y>.png", timeout=10,)res.raise_for_status()with open("output.png", "wb") as f: f.write(res.content)image/png, 512 × 512 Terrain-RGB. A position with no data returns a flat image at sea level (0 m)binary image/png
Try it
Tiles are 512 × 512 Terrain-RGB PNGs (mapbox encoding). Elevation in meters:
const elevation = -10000 + (r * 256 * 256 + g * 256 + b) * 0.1;A position outside coverage or without data does not return 204. It returns a 512 × 512 image for sea level (0 m), which MapLibre raster-dem can use directly.
Streets and buildings
Section titled “Streets and buildings”curl "https://lb.exptech.dev/api/v1/map/gsi/gsi.json"const res = await fetch("https://lb.exptech.dev/api/v1/map/gsi/gsi.json");if (!res.ok) throw new Error(`HTTP ${res.status}`);const data = await res.json();console.log(data);import requests
res = requests.get("https://lb.exptech.dev/api/v1/map/gsi/gsi.json", timeout=10)res.raise_for_status()print(res.json())TileJSON 2.0.0 (excerpt)
{ "tilejson": "2.0.0", "name": "ExpTech Studio GSI (Taiwan roads & buildings)", "format": "pbf", "minzoom": 0, "maxzoom": 14, "bounds": [ 114.28579, 10.32677, 122.3283, 26.43722 ], "attribution": "© OpenStreetMap contributors © OpenMapTiles", "tiles": [ "https://lb.exptech.dev/api/v1/map/gsi/{z}/{x}/{y}.pbf" ]}Try it
zZoom level. The range is minzoom / maxzoom in the TileJSON.
xXYZ tile column.
yXYZ tile row (north to south).
curl -o output.pbf --compressed "https://lb.exptech.dev/api/v1/map/gsi/<z>/<x>/<y>.pbf"const res = await fetch("https://lb.exptech.dev/api/v1/map/gsi/<z>/<x>/<y>.pbf");if (!res.ok) throw new Error(`HTTP ${res.status}`);const blob = await res.blob();import requests
res = requests.get("https://lb.exptech.dev/api/v1/map/gsi/<z>/<x>/<y>.pbf", timeout=10)res.raise_for_status()with open("output.pbf", "wb") as f: f.write(res.content)Mapbox Vector Tile (Content-Type: application/octet-stream). Some tiles are gzip-compressed (Content-Encoding: gzip)binary MVT
No tile at this position (no content)No response body
Try it
Built from OpenStreetMap data for Taiwan, using the OpenMapTiles layer schema, and covering Taiwan only. Common source-layers:
transportation, transportation_name, building, water, waterway, water_name, landcover, landuse, park, aeroway, aerodrome_label, boundary, place, poi, mountain_peak, housenumber.
Properties for each layer are in the OpenMapTiles documentation. When you use them, you must show “© OpenStreetMap contributors © OpenMapTiles”.
TileJSON fields
Section titled “TileJSON fields”| Field | Type | Description |
|---|---|---|
tilejson | string | TileJSON specification version. Currently 2.0.0. |
name | string | Tile set name. |
format | string | pbf (vector) or png (terrain). |
encoding | string | Terrain only. The value is mapbox (Terrain-RGB encoding). |
minzoom | integer | Minimum zoom level. |
maxzoom | integer | Maximum zoom level. Above this, scale up this level's tiles. |
bounds | number[4] | Coverage [west, south, east, north], in degrees. |
center | number[3] | Suggested initial center [longitude, latitude, zoom]. |
attribution | string | Attribution text that must be shown. |
tiles | string[] | Tile URL templates (absolute URLs). |
Advanced examples
Section titled “Advanced examples”Use all three tile sets in one MapLibre GL JS style. Create each source from the TileJSON url, and the zoom range and coverage are read automatically. The attribution inside the TileJSON is not the full suggested credit. Set attribution on the source yourself, as below (it replaces the TileJSON value):
const MAP = "https://lb.exptech.dev";
const map = new maplibregl.Map({ container: "map", center: [121, 23.7], zoom: 7, style: { version: 8, sources: { // Base map: global / city / town / tsunami exptech: { type: "vector", url: `${MAP}/api/v1/map/tiles/tiles.json`, attribution: "Data: ExpTech Studio (exptech.dev)" }, // Terrain: Terrain-RGB (mapbox encoding) terrain: { type: "raster-dem", url: `${MAP}/api/v1/map/terrain/terrain.json`, tileSize: 512, encoding: "mapbox", attribution: "Data: ExpTech Studio (exptech.dev)" }, // Taiwan streets and buildings: OpenMapTiles schema gsi: { type: "vector", url: `${MAP}/api/v1/map/gsi/gsi.json`, attribution: "Data: © OpenStreetMap contributors, © OpenMapTiles, ExpTech Studio (exptech.dev)" }, }, layers: [ { id: "background", type: "background", paint: { "background-color": "#1f2329" } }, { id: "land", type: "fill", source: "exptech", "source-layer": "global", paint: { "fill-color": "#3e4147" } }, { id: "county", type: "fill", source: "exptech", "source-layer": "city", paint: { "fill-color": "#4b4e55" } }, { id: "hillshade", type: "hillshade", source: "terrain", paint: { "hillshade-exaggeration": 0.3 } }, { id: "roads", type: "line", source: "gsi", "source-layer": "transportation", paint: { "line-color": "#8a8f98", "line-width": 1 } }, { id: "buildings", type: "fill", source: "gsi", "source-layer": "building", minzoom: 13, paint: { "fill-color": "#5a5e66" } }, { id: "town-line", type: "line", source: "exptech", "source-layer": "town", paint: { "line-color": "#a9b4bc", "line-width": 0.4 } }, { id: "county-line", type: "line", source: "exptech", "source-layer": "city", paint: { "line-color": "#a9b4bc", "line-width": 1 } }, ], },});- Use the
tilesURLs from the TileJSON. They are absolute URLs. The host name can differ from the TileJSON host. Do not replace it yourself. - When a vector tile (
.pbf) has no data, the response is 204 (no content), which is a different status from 404. MapLibre skips it. - Some vector tiles are returned with
Content-Encoding: gzip. Browsers and MapLibre decompress them. Other clients must handle it themselves (add--compressedto curl). - Terrain tiles are 512 × 512. Set
tileSize: 512on a MapLibreraster-demsource. - Zoom range and coverage follow the TileJSON response. The excerpts on this page can differ from the live values.
- Responses have no
Cache-Control. These tiles rarely change, so you can cache them on the client.
