A query giving a map layer
A query can return its records as a map layer: a FeatureCollection<G, P> (Interop), where G is the geometry type and P the type of each feature's properties.
A query can return its records as a map layer: a
FeatureCollection<G, P> (Interop), where G
is the geometry type and P the type of each feature's properties. A
GET route can then send the layer a mapping library fetches, as plain
RFC 7946 GeoJSON (Output and status).
Here are a bakery map's layer, and the bakeries within reach of a door:
use baking/entities { Bakery }use baking/types { BakeryName }/// What a bakery's pin on the map says: its identity goes in its/// properties.type BakeryPin = Object { id: Bakery.id name: BakeryName}
use baking/entities { Bakery }use baking/types { BakeryPin }/// Every bakery, as the map's layer.query bakery_map() -> FeatureCollection<Point, BakeryPin> { from: Bakery geometry: Bakery.spot order: [Bakery.name asc] limit: 500}
use baking/entities { Bakery }use baking/types { BakeryPin, SearchReach }/// The bakeries within reach of a door, nearest first, as a layer.query bakeries_near_map( doorstep: Point, max_trip: SearchReach) -> FeatureCollection<Point, BakeryPin> { from: Bakery geometry: Bakery.spot where: geo.within(Bakery.spot, doorstep, distance: max_trip) order: [geo.distance(Bakery.spot, doorstep) asc] limit: 20}
use baking/types { BakeryPin }use baking/queries { bakery_map }/// Bakeries, for the map.routes Bakeries { prefix: /bakeries /// Every bakery, as GeoJSON. GET / { authentication: RouteAuthentication.none output: FeatureCollection<Point, BakeryPin> run: bakery_map() }}
use baking/entities { Bakery }use baking/types { BakeryPin }use baking/actions { open_bakery }use baking/queries { bakeries_near_map, bakery_map }test "the map fetches the bakeries as GeoJSON" { var soho: Bakery = open_bakery( "Soho", Point { latitude: 51.5136 longitude: -0.1365 altitude: none } ) var bermondsey: Bakery = open_bakery( "Bermondsey", Point { latitude: 51.4979 longitude: -0.0637 altitude: none } ) var response: Response = request { method: HttpMethod.get path: "/bakeries" } assert response.status == 200 assert response.output == bakery_map() assert response.output == FeatureCollection { features: [ Feature { geometry: bermondsey.spot properties: BakeryPin { id: bermondsey.id name: bermondsey.name } }, Feature { geometry: soho.spot properties: BakeryPin { id: soho.id name: soho.name } } ] } var near_soho: FeatureCollection<Point, BakeryPin> = bakeries_near_map(soho.spot, 2km) assert list.map(near_soho.features, pin -> pin.properties.name) == ["Soho"]}
GET /bakeries answers {"type":"FeatureCollection","features":[...]},
each feature {"type":"Feature","geometry":{"type":"Point","coordinates":[-0.0637,51.4979]},"properties":{"id":"...","name":"Bermondsey"}}.
jadpo sql bakery_map prints its one statement, a SELECT of the
properties' columns and the geometry's.
The result type names the properties type, as List<Nearest<T>> names its
records. geometry: names the stored field that is each feature's
geometry, a G every record has. It has to be named because an entity may
hold several geometry fields, and the result type can't say which. P
must be an Object type, as a feature's properties always are
(Interop). Its fields are filled from the
record's fields of the same names, as any result type's are.
The query picks fields and computes nothing, so it is a bounded list like
any other: order: and limit:, with geo. conditions and an order by
geo.distance if it needs them (Queries).
geometry:names a field of the entity read, of exactly the layer's own geometry type, and nevernone, since a feature always has a geometry. So aPoint?field, aPolygonfield in a layer ofPoints, or any field in a layer ofGeometry(which would be a value built from it) isquery.geometry_field. A field the entity doesn't have isquery.unknown_field, as in a condition.- One bounded list.
order:andlimit:are required.page:is refused, since aFeatureCollectionhas no cursor, and so aremissing:,include:,group:andaggregate:, since a layer's properties are each record's own fields, one feature to a record. - Its properties. Each field of
Pmust be the record's field of that name, or the query isquery.result_field. A feature's identity goes in its properties, soPpicksidwhere the map needs it. - Field policy. A layer is sent as it is, so it is held to what an
Outputis held to (Field restrictions). Every caller who may read the records must be allowed to read each property, the geometry, and every field the conditions and order test, or the query ispolicy.output_field_unproved. This is proved where the query is declared, so an action returning the layer carries nothing more. - Only what a
GETcan ask for. AGET's input is its query string (One input), which can't hold aBoundingBox. So a layer of only what is in view would still need a body. The teaching example21-veg-box-roundssends its whole drop point layer withGET /drop-points.
A property the record has no field for:
type BakeryName = Text { max_length: 80}entity Bakery { id: Uuid name: BakeryName spot: Point}type BakeryPin = Object { bakery_id: Bakery.id name: BakeryName}query bakery_map() -> FeatureCollection<Point, BakeryPin> { from: Bakery geometry: Bakery.spot order: [Bakery.name asc] limit: 500}
error: `BakeryPin.bakery_id` isn't a field of `Bakery` --> main.jadpo:16:23 |16 | query bakery_map() -> FeatureCollection<Point, BakeryPin> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `bakery_id` can't be filled reason: A map layer fills its features' properties from `Bakery`'s fields of the same name (`id`, `name` and `spot`); nothing gives `bakery_id`. next: Remove `bakery_id` from `BakeryPin`, or name it after a field of `Bakery` (agent) rule: query.result_field help: https://jadpo.dev/docs/errors/query.result_fieldcheck failed: 1 error, 1 file
A property only the server may read:
type BakeryName = Text { max_length: 80}type PhoneText = Text { max_length: 20}entity Bakery { id: Uuid name: BakeryName spot: Point owner_phone: PhoneText { policy: FieldAccess.server_only } policy: { read: [Access.public] }}type BakeryPin = Object { name: BakeryName owner_phone: PhoneText}query bakery_map() -> FeatureCollection<Point, BakeryPin> { from: Bakery geometry: Bakery.spot order: [Bakery.name asc] limit: 500}
error: `bakery_map` sends `Bakery.owner_phone`, which not every caller may read --> main.jadpo:27:23 |27 | query bakery_map() -> FeatureCollection<Point, BakeryPin> { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ carries `owner_phone` reason: Anyone `Bakery`'s `read:` lets in may call `bakery_map`, anyone at all among them, and no client may read it, as it is `FieldAccess.server_only` `owner_phone`. An output has one shape for everyone, so a field some callers may not read can't be in it. next: Leave `owner_phone` out of `BakeryPin`, the features' properties (agent) rule: policy.output_field_unproved help: https://jadpo.dev/docs/errors/policy.output_field_unprovedcheck failed: 1 error, 1 file
Markdown for agents: /docs/a-query-giving-a-map-layer.md.Built from Jadpo main at a707efb.