Location Blocks
Everything RUAL knows about where something is, and what is near it. Coordinate maths, address lookup and points of interest, all answered from datasets on the node.
These blocks run offline, against datasets shipped with the node. No API keys, no rate limits, no per-call cost, and no request leaving the building. Lookups take microseconds, so you can run them inside loops and over whole inventories without thinking about it.
| Operation | Time |
|---|---|
Nearest lookup (kNN), for example places_nearest | 6 to 8 µs. A rare category is as fast as a common one. |
Text search, places_search | 11 ms |
| Family | What it does |
|---|---|
geo_point_* | Create, convert and do arithmetic on coordinates. |
geo_point_reverse_geocode, array_reverse_geocode, geo_point_from_city | Turn a coordinate into an address, or a place name into a coordinate. |
places_* | Find points of interest near a coordinate, search them, filter by them. |
osmworld_* | Fetch map geometry for rendering. |
Working With Coordinates
A geo point is an object with lat and lon. These blocks make and manipulate one. None of them touch a dataset: they are pure arithmetic.
| Block | Does what | Pins |
|---|---|---|
geo_point_lat_lng_to_point |
Builds a point from two numbers. | in lat, lngout geo_point |
geo_point_to_lat_lng |
Splits a point back into numbers. | in geo_pointout latitude, longitude |
geo_point_convert_to_object |
Casts a point to a plain object. | in geo_pointout object |
geo_point_validate |
Answers whether this is actually a usable coordinate. | in geo_pointout valid |
geo_point_branch |
Picks one of two points on a condition. | in then, else, conditionout geo_point |
geo_point_destination_point |
Moves a point a given distance along a bearing. | in geo_point, distance (metres), current_bearing (degrees)out geo_point |
geo_point_random_in_radius |
Returns a random point within a radius. | in geo_point, radiusout geo_point, success, error |
geo_point_define_reference / geo_point_use_reference |
Names a point once and reuses it elsewhere in the blueprint without rewiring. | see the block pages |
Use geo_point_validate as a guard before anything that costs work: a missing or malformed point is the most common reason a location flow silently does nothing. Use geo_point_random_in_radius to anonymise a location before showing it publicly, or to generate plausible test data.
Geocoding: Coordinates To Places, And Back
A coordinate becomes an address
geo_point_reverse_geocode takes a geo_point and returns street, city, neighborhood, province, country, country_code, success and error.
neighborhoodis filled only when the point sits inside a larger city. A point in Ittersum returns city Zwolle and neighbourhood Ittersum, while a point in Siebengewald returns that as the city, because it is a village in its own right rather than part of one.streetis the nearest named road and needs the road network dataset loaded.
Many coordinates at once
array_reverse_geocode enriches a whole array in one call, with no loop. You give it the path to the geo point inside each item (for example location.location) and the paths to write each result to. Leave an output key empty to skip that field.
| Pin | Purpose |
|---|---|
array | The items to enrich. |
location_key | Path to the geo point inside each item. |
location_fallback_key | Second path, for the common case where some records carry a verified coordinate and others only a rough one. |
street_key, city_key, neighborhood_key, province_key, country_key, country_code_key | Where to write each resolved field. |
array (out) | The enriched items. |
unresolved (out) | Items whose point was missing or outside coverage. |
Always check unresolved
Items whose point was missing or outside coverage come back unchanged. If you write the array to a store without looking at unresolved, you will not notice.
A place name becomes a coordinate
geo_point_from_city takes city and country_code, and returns geo_point, radius, province, country, country_code, success and error.
radiusis the city's approximate extent in metres, derived from population. Handy for "is this roughly in Breda" without needing a boundary.- Pass
country_codeto disambiguate: there is a Bergen in NL, NO and DE.
Terrain and infrastructure
| Block | Answers | Pins |
|---|---|---|
geo_point_land_cover |
What kind of ground is this? | in geo_pointout type (urban, agricultural, forest, grassland, wetland, water, bare, coastal), code, success, error |
geo_point_nearby |
Is a road or railway within X metres? | in geo_point, type (road/rail), max_distanceout is_nearby, distance, nearest_point, success, error |
geo_point_snap |
Pull a point onto the nearest road or railway. | in geo_point, typeout geo_point, distance, success, error |
On geo_point_snap, distance tells you how far the point moved. A large value means the original point was nowhere near a road, which usually means the input was wrong.
Places: Points Of Interest
The places_* blocks are backed by Overture Maps Places, a source of roughly 60 million points of interest in total. The Netherlands is what a node loads by default: roughly 737,000 Dutch shops, schools, stations, restaurants, hospitals and so on. Three ideas explain most of how these blocks behave, and they are worth reading before wiring anything.
Need another country?
Only the Dutch extract is loaded by default, but the source covers the rest of the world. Other countries can be added to your node on request: ask through support and we will build and ship that extract.
Distances, never yes or no
Every spatial block returns the distance in metres, not a boolean. That is deliberate: holding the distances, every question is arithmetic you can do yourself, with no second query.
within 200 m of a school? nearest_distance_m <= 200
within 317 m? nearest_distance_m <= 317
at least 3 schools within 500 m? results[2].distance_m <= 500
NOT within 500 m of a school? nearest_distance_m > 500So a radius slider in your UI never re-queries, and no radius is ever baked into stored data where changing it would mean reprocessing everything.
Categories accept plain Dutch
Anywhere a block has a categories pin, you can pass group ids or ordinary words. Regular plurals are handled.
| Group id | Also accepted |
|---|---|
primary_education | basisschool, basisscholen |
bar | kroeg, kroegen |
train_station | treinstation, station |
places_categories lists everything available, and places_resolve_category turns one word into a group id.
Duplicates are collapsed, and the result says so
Overture is not deduplicated. Around Breda station the raw data holds three separate records for one station: Station Breda Centraal, Gare de Bréda and NS Station Breda. With dedup on (the default) those merge into one result, reported as merged_count: 3 with merged_names listing all three. Meanwhile the bike shed, the car park, the park called Happy Station, the Playmobil museum and the neighbourhood council all stay separate: they are different things that happen to share a word.
places_within reports both raw_count and count. The gap between them tells you how much duplication the source data carries at that spot. Treat count as a tidy search result, not a census: nationally the data holds 1,432 train_station records against roughly 400 real stations.
Finding places
| Block | Does what | Pins |
|---|---|---|
places_nearest |
The one you will use most: nearest matches with their distance. | in geo_point (required), categories, max_results (10), max_distance (0 = unbounded), min_confidence, dedup (true)out results, nearest, nearest_distance_m, count, success, error |
places_within |
Everything inside a radius. | in geo_point (required), radius_m (required), categories, limit (100), sort (distance/confidence/name), min_confidence, dedupout results, count, raw_count, truncated, success, error |
places_in_bbox |
Everything on screen. | in north_east (required), south_west (required), categories, limit (500), min_confidence, dedupout results, count, raw_count, truncated, success, error |
places_density |
How built up is this area? | in geo_point (required), radius_m (500), categoriesout total, raw_total, per_km2, by_group, top_groups, classification, success, error |
Results from places_in_bbox carry no distance: a box has no centre. Use places_nearest when you need distances. On places_density, classification is dense_urban, urban, suburban or rural, driven by the per square kilometre rate, so it does not change when you change the radius.
Working over a whole inventory
These blocks exist so you never write a loop.
| Block | Does what | Pins |
|---|---|---|
places_enrich |
Adds nearest-place data to every item in a list, in one call. | in items (required), point_path, categories (required), max_distance, count_radius, include, output_path (places)out items, count, unresolved, success, error |
places_filter |
Keeps or drops items by a places rule, without touching a store. | in items (required), point_path, filter (required), mode (keep/remove), annotate, output_pathout items, count, removed_count, success, error |
places_facet |
Answers "how many of my objects are near a hospital?" | in items (required), point_path, categories, radius_m (500), filtersout counts, total, success, error |
Each item that places_enrich touches gets one entry per category:
{
"places": {
"hospital": {
"count": 0,
"distance_m": 1840,
"id": "...",
"name": "..."
},
"primary_education": {
"count": 3,
"distance_m": 212,
"id": "...",
"name": "..."
}
}
}Items are copied, never modified in place, and every item comes back whether or not it had a usable coordinate. Check unresolved before writing back. places_facet counts items, not places: an object with three hospitals nearby counts once.
Search and typed questions
places_search is one search box over places and towns. It takes query (required), near, bias_radius_m (25000), kinds (poi/settlement), categories, limit (10), typo_tolerance (2), min_confidence and dedup, and returns results, best, count, success and error.
- Handles accents (
BrédafindsBreda), typos (amsterdm centraal) and partial words as you type. - Towns outrank businesses, so
Bredagives you the municipality rather than one of the roughly 2,000 companies with Breda in the name. - Pass
near(your map centre) to prefer nearby results. - Results carry
kind(poiorsettlement),subtitle(Breda, Noord-Brabant, ortreinstation · Amsterdam) andscore: enough to render a dropdown without a second lookup.
places_parse_query turns typed Dutch or English into a filter. It takes query (required), language (auto), default_radius_m (500) and near, and returns kind, filter, negated, category_group, category_label, radius_m, min_count, geo_point, place_name, confidence, unparsed, success and error.
| Typed | Parsed as |
|---|---|
binnen 300m van een basisschool | proximity, primary_education, 300 m |
niet bij scholen | proximity, education, negated |
minimaal 3 kroegen binnen 500m | proximity, bar, 500 m, at least 3 |
wijk Jordaan | an area, with coordinates |
Station Breda | a place, with coordinates |
Parsing is rule-based and deterministic: no model call, no network, the same answer every time. It never guesses silently, so anything it could not read comes back in unparsed with a lower confidence, and you can fall back to plain search rather than acting on half a filter.
places_match tests one point against a filter. It takes geo_point (required), filter (required) and max_distance, and returns matches, distance_m, count, nearest, reason, success and error. Both distance_m and count come back whether or not it matched, so a radius slider can re-answer from the result without calling again. reason is a readable Dutch line you can show the user, for example 3× basisschool binnen 500m; dichtstbij 212m.
Categories and dataset info
| Block | Does what | Pins |
|---|---|---|
places_categories |
Builds your category dropdown. | in search, group, language (nl)out groups, count, success, error |
places_resolve_category |
Resolves one word to a group, and returns alternatives when a word is ambiguous (school) rather than picking one. |
in text (required), languageout group, label, confidence, alternatives, success, error |
places_get |
Re-resolves a stored id. | in id (required)out place, found, success, error |
places_dataset_info |
Reports what data this node is running. Worth putting on an admin screen. | no inputs out loaded, release, vocabulary_version, record_count, group_count, category_count, bbox, built_at, success, error |
Two blocks want a different out-pin than success
Branch places_get on found, not success: an id can vanish between dataset releases, and that is a normal outcome rather than an error. Branch places_dataset_info on loaded, not success: it is the only places block that answers at all when no dataset is loaded.
Map Geometry
osmworld_tile (in z, x, y, kinds) and osmworld_bbox (in bbox, or south/west/north/east, plus kinds and simplify) both return a GeoJSON FeatureCollection of roads, buildings, water and greenery for drawing a map, on a geojson out-pin alongside count.
These two are the exception
Unlike everything else on this page, the osmworld_* blocks call Overpass over the network and cache to disk. They have flow pins and they are not free. Budget for latency and failure the way you would for any outbound call.
Recipes
Store how far every object is from a school
query objects
-> places_enrich items=results, point_path=general.location,
categories=basisschool, output_path=places
-> mutations write items backAfterwards an ordinary query_bool_range_field on places.primary_education.distance_m filters your inventory. No further place lookups, and any radius works, because the distance is stored rather than a flag.
A search box that understands what was typed
places_parse_query query = <what the user typed>
|- kind = place / area -> geo_point -> move the map
|- kind = proximity -> filter -> places_filter over your objectsUse confidence and unparsed to decide whether to trust the filter or fall back to places_search.
How many of my objects are near a hospital?
query objects -> places_facet categories = ziekenhuis, apotheek
radius_m = 500counts gives you the number per category, and total the number of items evaluated.
Guarding a location flow
geo_point_validate -> if not valid, stop
-> geo_point_reverse_geocode (address)
-> places_nearest (what is around it)Reading The Results Honestly
The data is good, but it is not a register. These are properties of the source, so meet them here rather than in front of a client.
| What | Why it matters |
|---|---|
| Education overcounts by about 6% | 6,704 records against DUO's roughly 6,300 primary schools. That is exactly the filter with legal consequences, so treat it as indicative rather than compliance grade, and check the official register before anything binding. |
| Counts are indicative | Deduplication tidies search results, it does not resolve entities. raw_count sits next to count so the gap stays visible. |
confidence is uncalibrated | Median 0.80. Filtering on it may hide coverage as easily as it removes noise, which is why it defaults to no filter. |
brand is sparse | Match chains on name. Kruidvat reads 1,010 by name and 27 by brand. |
| Areas are not official | wijk Jordaan resolves against OSM neighbourhood data, not CBS boundaries, and carries no official code. |
Every block on this page reports failures on its error pin rather than stopping the blueprint, so branch on success and handle the code.
| Error | Means |
|---|---|
PLACES_NOT_AVAILABLE | No places dataset on this node. |
GEO_POINT_MISSING_LAT_OR_LON | The point had no usable coordinate. |
RADIUS_REQUIRED | A radius pin was left empty on a block that needs one. |
CATEGORIES_REQUIRED | A categories pin was left empty on a block that needs one. |
FILTER_REQUIRED | places_filter or places_match ran without a filter. |
QUERY_REQUIRED | places_search or places_parse_query ran without a query. |
The Datasets Behind All This
The datasets are shared by every entity on the node and loaded from disk at startup. They are gated by geo.require_data: with that on (the default) a node refuses to boot rather than quietly serving blocks backed by missing data.
| File | Powers | Source |
|---|---|---|
| (embedded) | geo_point_reverse_geocode, geo_point_from_city, array_reverse_geocode | GeoNames + OSM |
network.bin | geo_point_nearby, geo_point_snap, the street output | OpenStreetMap |
landcover.bin | geo_point_land_cover | ESA WorldCover |
places.bin | every places_* block | Overture Maps Places |
Because the files are loaded rather than compiled in, refreshing the data needs no new binary: just the file and a restart. You do not have to remember to refresh the places data either. The node checks the published Overture releases at startup and once a day after, and warns when it has fallen three releases behind.
places dataset is STALE: running release 2026-01-01.0, latest is 2026-07-22.0
(6 releases behind). Refresh it.Next Steps
Geopoint Blocks The full geopoint group in the block catalog, with every pin. Block Quick Reference The blocks you reach for daily, grouped by what they do. Storages Where enriched items are written back, and how to query them. Error Code Reference What each error code means and what usually causes it.Frequently asked
Does RUAL geocoding need a Google Maps API key?
No. Reverse geocoding, city lookup, land cover and the places search all run offline against datasets shipped with the node, so there is no API key, no rate limit and no per-call cost. Only the osmworld blocks, which fetch map geometry for rendering, call out over the network.
How do I find out whether a location is near a school in RUAL?
Use places_nearest with the category basisschool. It returns nearest_distance_m, the distance in metres, rather than a yes or no. Compare that number to whatever radius you need, so changing the radius never means running the query again.
How do I add the distance to the nearest school to every document in a RUAL storage?
Run places_enrich over the search result with the path to the geo point inside each item, then write the items back with mutations. The stored distance can then be filtered with an ordinary range query, and it works for any radius because the distance is stored rather than a flag.
Does RUAL have places data for countries other than the Netherlands?
A node loads the Dutch extract by default, roughly 737,000 points of interest, out of a source that holds around 60 million worldwide. Other countries can be added to your node on request: ask through support and the extract is built and shipped to you.
