BeMapAPI
BeMap 4.1.0

Get a satellite image with WMS 1.3.0#

This tutorial shows how to obtain a satellite raster image from BeMap with the WMS 1.3.0 protocol, using the herehlp geo-server.

The request used all along this tutorial is the following one: a single 256 × 256 satellite tile of the French Riviera, north of Antibes:

json
GET https://bemap.benomad.com/bgis/wms?REQUEST=GetMap&SERVICE=WMS&VERSION=1.3.0&FORMAT=image%2Fpng&STYLES=&TRANSPARENT=false&geoserver=herehlp&LAYERS=here-satellite.day&TILED=true&WIDTH=256&HEIGHT=256&CRS=EPSG%3A3857&BBOX=786384.1469978951%2C5411741.602590479%2C787607.1394504579%2C5412964.595043042

Everything that makes this request work (and that makes a hand-written one fail) is explained below.

Summary#

  1. Prerequisites
  2. Anatomy of the request
  3. Selecting the satellite layer
  4. Building a valid BBOX (the tricky part)
  5. Sending the request
  6. Reading the response
  7. Displaying satellite tiles in a map client
  8. Troubleshooting

1. Prerequisites#

ItemValue
End pointhttps://[environment]/bgis/wms
Environmentsbemap.benomad.com (production) or bemap-preprod.benomad.com (preproduction)
ProtocolWMS 1.3.0
Geo-serverherehlp
CredentialsAn account allowed to use the herehlp geo-server

Authentication is the standard BeMap one (HTTP Authorization: Basic, X-Auth-ID, SESSION cookie, or the deprecated appid / appcode URL parameters). See the authentication page.

herehlp must be named explicitly: send the geoserver=herehlp parameter on every request.

2. Anatomy of the request#

GetMap is a plain HTTP GET: all the parameters live in the query string, and every value must be URL-encoded (image/png → image%2Fpng, EPSG:3857 → EPSG%3A3857, the , of the BBOX → %2C).

ParameterValue in the exampleRole
SERVICEWMSMandatory. Protocol name.
VERSION1.3.0Mandatory. Protocol version.
REQUESTGetMapMandatory. Operation returning an image.
CRSEPSG:3857Mandatory. Coordinate reference system of the BBOX.
BBOXminx,miny,maxx,maxyMandatory. Area to render, expressed in the CRS units.
WIDTH / HEIGHT256 / 256Mandatory. Size of the returned image, in pixels.
LAYERShere-satellite.dayThe satellite layer, see chapter 3.
STYLES(empty)Rendering style. Empty means "use the layer default".
FORMATimage/pngOutput format: image/png, image/png24 or image/jpeg, the satellite tile is JPEG in every case, see chapter 6. image/gif answers an error on herehlp. Defaults to image/png.
TRANSPARENTfalseKeep it false for a satellite basemap.
geoserverherehlpBeMap extra parameter. Selects the data provider.
TILEDtrueNot a WMS parameter: it is emitted by OpenLayers / Leaflet and simply ignored by BeMap. Harmless.

Two optional parameters are worth knowing:

  • EXCEPTIONS: how errors are reported. Accepted values are XML (default), INIMAGE (the error message is painted inside the returned image) and BLANK (a uniform image).
  • LANGUAGE: ISO 639-1 language code used for the map labels. It has no visible effect on a pure satellite layer.

3. Selecting the satellite layer#

The satellite imagery is served by the herehlp geo-server. The layer name is:

LAYERS=here-satellite.day

Good to know:

  • The here- prefix is optional: LAYERS=satellite.day is equivalent. The prefix is stripped by the server before the provider call.
  • STYLES accepts the same values as LAYERS, and takes precedence over LAYERS when both are filled. Sending an empty STYLES and the layer name in LAYERS, as in the example, is the recommended way.
  • For backward compatibility, hybrid.day and terrain.day are also mapped to the satellite rendering, while normal.day and default are mapped to the standard rendering.
  • Depending on the deployment, the herehlp geo-server can be configured in satellite only mode. In that case every GetMap on this geo-server returns satellite imagery, whatever LAYERS and STYLES contain. Production is in that mode (measured on 29 September 2026: normal.day, default, an unknown name and an empty LAYERS all return the satellite tile).
  • GetCapabilities on geoserver=herehlp does not advertise a layer list: use the layer names documented here.

4. Building a valid BBOX (the tricky part)#

Satellite imagery is made of pre-rendered raster tiles, not of a map drawn on demand. BeMap therefore only serves GetMap requests whose BBOX matches exactly one cell of its internal tile grid. A free-form bounding box (an arbitrary rectangle around a city, for instance) is rejected with a rendering error.

The tile grid#

PropertyValue
ProjectionWeb Mercator (EPSG:3857, also accepted as EPSG:3785 and EPSG:900913)
World extent-20037508.34 … 20037508.34 on both axes
Tile size256 × 256 pixels
Zoom levels0 … 20
Resolution at level z156543.03390625 / 2^z metres per pixel

Two direct consequences:

  • WIDTH and HEIGHT must be 256. Larger sizes are rejected by the tile cache rules.
  • TRANSPARENT must stay false to benefit from the tile cache: a transparent request bypasses it.

Formulas#

For a tile (z, x, y) given in the usual XYZ convention (y counted from the north):

javascript
var ORIGIN = -20037508.34;                     // Web Mercator world origin
var SPAN   = 40075016.68 / Math.pow(2, z);     // size of one tile, in metres

var minx =  ORIGIN + x * SPAN;
var maxx =  minx + SPAN;
var miny = -ORIGIN - (y + 1) * SPAN;
var maxy =  miny + SPAN;

var bbox = minx + ',' + miny + ',' + maxx + ',' + maxy;

And to go from a geographic position to a tile:

javascript
function lonLatToTile(lon, lat, z) {
    var n = Math.pow(2, z);
    var rad = lat * Math.PI / 180;
    return {
        x: Math.floor((lon + 180) / 360 * n),
        y: Math.floor((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2 * n),
        z: z
    };
}

lonLatToTile(7.0697, 43.6559, 15);   // → { x: 17027, y: 11958, z: 15 }

Worked example#

The request at the top of this page is exactly the tile z = 15, x = 17027, y = 11958:

StepResult
SPAN at level 1540075016.68 / 32768 = 1222.9924523925781 m
Resolution1222.99245… / 256 = 4.777314267… m/pixel
minx-20037508.34 + 17027 × 1222.99245… = 786384.146…
miny20037508.34 - 11959 × 1222.99245… = 5411741.601…
maxx, maxyminx + SPAN, miny + SPAN
Geographic footprint≈ 7.0642, 43.6520 → 7.0752, 43.6599 (WGS84)

The server accepts a tolerance of one pixel on the tile corners, which is why the values sent by a map client (786384.1469978951, …) and the values recomputed above are accepted even though their last decimals differ.

If you use CRS=EPSG:4326#

WMS 1.3.0 changed the axis order for geographic CRS. With CRS=EPSG:4326 the bounding box must be written latitude first:

&CRS=EPSG%3A4326&BBOX=minLat%2CminLon%2CmaxLat%2CmaxLon

With EPSG:3857 (and the other Mercator codes) the order stays minx,miny,maxx,maxy: easting first. This axis inversion is the main incompatibility between a WMS 1.1.1 request and its 1.3.0 counterpart. Always send VERSION=1.3.0: without it BeMap reads the request as 1.1.1, which takes SRS rather than CRS and reads an EPSG:4326 box longitude first.

5. Sending the request#

With cURL#

curl -o tile.png \
  -H "Authorization: Basic <base64 of account:apikey>" \
  "https://bemap.benomad.com/bgis/wms?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&geoserver=herehlp&LAYERS=here-satellite.day&STYLES=&FORMAT=image%2Fpng&TRANSPARENT=false&WIDTH=256&HEIGHT=256&CRS=EPSG%3A3857&BBOX=786384.1469978951%2C5411741.602590479%2C787607.1394504579%2C5412964.595043042"

From a browser#

Once authenticated on the BeMap host (form login or SESSION cookie), pasting the full URL in the address bar displays the tile directly.

6. Reading the response#

On success the server returns the raw image bytes with:

HeaderContent
Content-TypeThe MIME type of FORMAT, for example image/png
Last-ModifiedDate of the cached tile, when it comes from the tile cache
Cache-ControlServer-side caching policy

⚠️ The satellite tile is JPEG, whatever the label. Measured on production (29 September 2026): with FORMAT=image/png or image/png24 the bytes are a JPEG image (they start with FF D8 FF E0) sent as Content-Type: image/png; with image/jpeg the same bytes come as image/jpeg. Code that reads the tile itself must go by its first bytes, not by the header.

Sending an If-Modified-Since header on the next call for the same tile makes the server answer 304 Not Modified with no payload: use it to spare bandwidth in a tile-heavy client.

On failure the response is a WMS service exception, formatted according to EXCEPTIONS (XML by default), with status 200: an XML exception comes as text/xml, an INIMAGE or BLANK one as a PNG. Check the Content-Type, not the status. The exception code is FrontendException for each exception listed in chapter 8; the message says what went wrong.

7. Displaying satellite tiles in a map client#

Both examples below produce exactly the request documented in this tutorial, tile after tile, as the user pans and zooms.

OpenLayers#

javascript
var satellite = new ol.layer.Tile({
    source: new ol.source.TileWMS({
        url: '/bgis/wms',
        params: {
            'VERSION':     '1.3.0',
            'geoserver':   'herehlp',
            'LAYERS':      'here-satellite.day',
            'STYLES':      '',
            'FORMAT':      'image/png',
            'TRANSPARENT': false,
            'TILED':       true
        }
    })
});

var map = new ol.Map({
    target: 'map1',
    layers: [satellite],
    view: new ol.View({
        projection: 'EPSG:3857',
        center: ol.proj.transform([7.0697, 43.6559], 'EPSG:4326', 'EPSG:3857'),
        zoom: 15,
        maxZoom: 20
    })
});

ol.source.TileWMS emits tile-aligned bounding boxes by construction, so the grid constraint of chapter 4 is satisfied automatically.

Leaflet#

javascript
var map = L.map('map1').setView([43.6559, 7.0697], 15);

L.tileLayer.wms('/bgis/wms', {
    version:     '1.3.0',
    geoserver:   'herehlp',
    layers:      'here-satellite.day',
    styles:      '',
    format:      'image/png',
    transparent: false,
    tiled:       true,
    maxZoom:     20
}).addTo(map);

From outside the BeMap host, each tile request must be authenticated: send the Authorization: Basic header (a custom tile loader in OpenLayers or Leaflet), or, on a public page, fetch the tiles through your own back end so that the key never reaches the browser. The appid / appcode URL parameters are deprecated and put the key in every tile URL.

8. Troubleshooting#

SymptomCauseFix
Request contains a CRS not offered by the server…CRS missing, misspelled or not supportedUse EPSG:3857 (or EPSG:4326, EPSG:4979, EPSG:3785, EPSG:900913)
Request does not include a sample dimension value… / Request contains an invalid sample dimension value.BBOX, WIDTH or HEIGHT missing or malformedThe four BBOX values are required, comma-separated and URL-encoded
Invalid FormatUnsupported FORMATimage/png, image/png24, image/jpeg
Unable to perform mapping with provider HereHlpThe BBOX does not match a tile of the grid (Current x value is too far from tile corner…), or its resolution does not match a zoom level (Resolution not found); or FORMAT=image/gif (HereHlp returned an HTTP error with code 400)Recompute the BBOX with the formulas of chapter 4; use image/png or image/jpeg
Request not valided by ValidatorWIDTH or HEIGHT greater than 256: the message names neitherRequest 256 × 256 tiles and assemble them client-side
The returned image is a standard map, not satelliteLayer name not recognised, or STYLES overriding LAYERSSend LAYERS=here-satellite.day with an empty STYLES
401 or a login page is returnedMissing or invalid credentialsSee authentication
Rendering works on another geo-server but not hereThe account is not allowed to use herehlpContact your BeNomad account manager

See also#