A query giving a map layer
A query can return its records as a map layer: a
FeatureCollection<G, P> (GeoJSON and other formats), 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
(GeoJSON and other formats). 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 (Finding records by place).
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,nameandspot); nothing givesbakery_id. - next
- Remove
bakery_idfromBakeryPin, or name it after a field ofBakery(agent) - rule
- query.
result_ field - help
- https://jadpo.
dev/docs/errors/query. result_ field
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'sread:lets in may callbakery_map, anyone at all among them, and no client may read it, as it isFieldAccess.server_onlyowner_phone. An output has one shape for everyone, so a field some callers may not read can't be in it. - next
- Leave
owner_phoneout ofBakeryPin, the features' properties (agent) - rule
- policy.
output_ field_ unproved - help
- https://jadpo.
dev/docs/errors/policy. output_ field_ unproved
Markdown for agents: /docs/a-query-giving-a-map-layer.md.Built from Jadpo main at aee2e8f.