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.

OperationTime
Nearest lookup (kNN), for example places_nearest6 to 8 µs. A rare category is as fast as a common one.
Text search, places_search11 ms
FamilyWhat it does
geo_point_*Create, convert and do arithmetic on coordinates.
geo_point_reverse_geocode, array_reverse_geocode, geo_point_from_cityTurn 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.

BlockDoes whatPins
geo_point_lat_lng_to_point Builds a point from two numbers. in lat, lng
out geo_point
geo_point_to_lat_lng Splits a point back into numbers. in geo_point
out latitude, longitude
geo_point_convert_to_object Casts a point to a plain object. in geo_point
out object
geo_point_validate Answers whether this is actually a usable coordinate. in geo_point
out valid
geo_point_branch Picks one of two points on a condition. in then, else, condition
out 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, radius
out 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.

  • neighborhood is 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.
  • street is 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.

PinPurpose
arrayThe items to enrich.
location_keyPath to the geo point inside each item.
location_fallback_keySecond 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_keyWhere 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.

  • radius is the city's approximate extent in metres, derived from population. Handy for "is this roughly in Breda" without needing a boundary.
  • Pass country_code to disambiguate: there is a Bergen in NL, NO and DE.

Terrain and infrastructure

BlockAnswersPins
geo_point_land_cover What kind of ground is this? in geo_point
out 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_distance
out is_nearby, distance, nearest_point, success, error
geo_point_snap Pull a point onto the nearest road or railway. in geo_point, type
out 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.

Questions you answer from the result
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 > 500

So 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 idAlso accepted
primary_educationbasisschool, basisscholen
barkroeg, kroegen
train_stationtreinstation, 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

BlockDoes whatPins
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, dedup
out 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, dedup
out results, count, raw_count, truncated, success, error
places_density How built up is this area? in geo_point (required), radius_m (500), categories
out 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.

BlockDoes whatPins
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_path
out 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), filters
out counts, total, success, error

Each item that places_enrich touches gets one entry per category:

places_enrich, per item
{
  "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éda finds Breda), typos (amsterdm centraal) and partial words as you type.
  • Towns outrank businesses, so Breda gives 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 (poi or settlement), subtitle (Breda, Noord-Brabant, or treinstation · Amsterdam) and score: 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.

TypedParsed as
binnen 300m van een basisschoolproximity, primary_education, 300 m
niet bij scholenproximity, education, negated
minimaal 3 kroegen binnen 500mproximity, bar, 500 m, at least 3
wijk Jordaanan area, with coordinates
Station Bredaa 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

BlockDoes whatPins
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), language
out 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

Enrich once, filter forever
query objects
  -> places_enrich   items=results, point_path=general.location,
                     categories=basisschool, output_path=places
  -> mutations       write items back

Afterwards 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

Parse first, then branch
places_parse_query  query = <what the user typed>
  |- kind = place / area  -> geo_point   -> move the map
  |- kind = proximity     -> filter      -> places_filter over your objects

Use 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?

One call over the whole inventory
query objects -> places_facet  categories = ziekenhuis, apotheek
                               radius_m   = 500

counts gives you the number per category, and total the number of items evaluated.

Guarding a location flow

Validate before you spend work
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.

WhatWhy 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 indicativeDeduplication tidies search results, it does not resolve entities. raw_count sits next to count so the gap stays visible.
confidence is uncalibratedMedian 0.80. Filtering on it may hide coverage as easily as it removes noise, which is why it defaults to no filter.
brand is sparseMatch chains on name. Kruidvat reads 1,010 by name and 27 by brand.
Areas are not officialwijk 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.

ErrorMeans
PLACES_NOT_AVAILABLENo places dataset on this node.
GEO_POINT_MISSING_LAT_OR_LONThe point had no usable coordinate.
RADIUS_REQUIREDA radius pin was left empty on a block that needs one.
CATEGORIES_REQUIREDA categories pin was left empty on a block that needs one.
FILTER_REQUIREDplaces_filter or places_match ran without a filter.
QUERY_REQUIREDplaces_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.

FilePowersSource
(embedded)geo_point_reverse_geocode, geo_point_from_city, array_reverse_geocodeGeoNames + OSM
network.bingeo_point_nearby, geo_point_snap, the street outputOpenStreetMap
landcover.bingeo_point_land_coverESA WorldCover
places.binevery places_* blockOverture 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.

Startup log
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.