Skip to content
AI Agent
QGIS
Tutorial

Spatial Join in QGIS: By Location, Nearest, or With AI

Published

A street map of Lyon's 8th arrondissement with red school points, each linked by a black line to its nearest blue tram stop, and the words 710 m, median distance from a Lyon school to a tram stop.

A spatial join copies the fields of one layer onto another based on where the features are, not on a shared ID. In QGIS, open Vector > Data Management Tools > Join Attributes by Location…, pick the layer that should get the new fields, the layer to take them from and a rule such as "intersect", then run it. For "which is closest" questions, use Join Attributes by Nearest instead, or type the whole task in one sentence to AI Agent.

What you wantToolTime on our test
Give each point the name of the area it falls inJoin Attributes by Location0.21 s, 382 schools
The same, typed as one sentenceAI Agent22 s, count and nearest together
Count or sum points per areaJoin Attributes by Location (Summary)0.02 s, 9 areas
Find the closest feature and its distanceJoin Attributes by Nearest0.06 s, 382 schools

The test data

Every number in this guide comes from one run on real open data for Lyon, France, on 15 October 2026:

  • 9 arrondissements (the city's districts) and 382 schools from OpenStreetMap.
  • 106 tram stops from OpenStreetMap.
  • 103,910 buildings from the IGN BD TOPO, the French national topographic database.

All layers are in Lambert-93 (EPSG:2154), a projected system in metres. The times come from QGIS 3.44 on a MacBook Air M4.

Method 1: Join attributes by location

This is the classic spatial join. Here, each school gets the name of its arrondissement.

Open the tool

Vector > Data Management Tools > Join Attributes by Location…. You can also search "join attributes" in the Processing Toolbox (Ctrl+Alt+T).

Pick the two layers

Join to features in is the layer that gets the new fields, here schools. By comparing to is the layer you take them from, here arrondissements.

Tick a predicate

intersect is the safe default for points in polygons. The section below shows what each box means.

Choose the fields and the join type

In Fields to add, keep only what you need, here name. Set Join type to one-to-one, and add a prefix such as arr_ so the new field is easy to spot.

Run it

Leave Joined layer as a temporary layer to check the result first, then save it as a GeoPackage.

The QGIS Join attributes by location dialog. Join to features in: schools. Predicate: intersect is ticked. By comparing to: arrondissements. Fields to add: name. Join type: Take attributes of the first matching feature only (one-to-one). Joined field prefix: arr_.
The join we ran: schools get the name of the arrondissement they fall in. All 382 matched in 0.21 seconds.

Tick Discard records which could not be joined only if you want to drop the misses. Leave it off at first: the empty rows tell you what went wrong.

Use it when: you want one or a few fields from the feature each object sits in or touches.

Skip it when: the features never touch, like a school and a tram stop. That is Method 4.

Method 2: Ask AI Agent in one sentence

AI Agent is our QGIS plugin that runs tasks from a typed request. It reads your layer names and fields, picks the tools and runs them in your project.

We gave it this, with the same Lyon layers loaded:

Count the schools in each arrondissement, then give every school the name of its nearest tram stop and the distance to it in metres.

It ran two QGIS processing tools, then a short Python step that wrote the two new fields into the schools layer. The panel showed "Worked for 22 s". It asked once for permission, because it was about to edit the GeoPackage on disk. Its counts matched our manual run exactly: 382 schools, from 27 in the 2nd and 4th to 56 in the 5th.

QGIS with the Lyon arrondissements filled in orange and school points, and the AI Agent panel on the right. The request reads: Count the schools in each arrondissement, then give every school the name of its nearest tram stop and the distance to it in metres. The answer lists the counts per arrondissement, 382 schools in total, and says all 382 schools got nearest_tram and tram_distance_m, with distances from 49.6 to 4,949.2 m.
The same two joins from one sentence. The answer links to the new layer and lists what changed.

Use it when: you know the question but not which of the four join tools answers it, or you chain several steps.

Skip it when: you run the same join every week. Save it as a model or a Python script once.

Price: free plan, no card. Pro is €49 a month excluding VAT.

Method 3: Count and summarize with a spatial join

Method 1 gives each school its district. The reverse question, how many schools each district holds, needs the summary version.

Search Join attributes by location (summary) in the Processing Toolbox. Put the polygons in Join to features in, the points in By comparing to, and pick statistics in Summaries to calculate: count, sum, mean, median, minimum, maximum and more. QGIS adds one field per statistic, named like osm_id_count.

Map of Lyon's nine arrondissements coloured from light yellow to dark orange by school count, each labelled: 9e 38, 4e 27, 1e 33, 6e 41, 5e 56, 2e 27, 3e 54, 7e 53, 8e 53. Red dots mark the schools.
Schools per arrondissement from one summary join: 0.02 seconds.

It also sums numbers. On the IGN buildings, one run gave each arrondissement its building count and the dwellings recorded in them: 9,474 buildings and 51,743 dwellings in the 3rd, the largest of both.

For a plain count, Vector > Analysis Tools > Count Points in Polygon… does the same with fewer settings.

Use it when: the answer is a number per area: count, total, average.

Skip it when: you need the list of matches itself. Use Method 1 with one-to-many.

Method 4: Join attributes by nearest

When features never touch, join them by distance. Search Join attributes by nearest in the Processing Toolbox.

Set Input layer to schools, Input layer 2 to tram_stops, and Maximum nearest neighbors to 1. QGIS adds the stop's fields plus a distance field, and the coordinates of both ends.

Street map of Lyon's 8th arrondissement. Red school points are each linked by a black line to the nearest blue tram stop. Several schools share the same stop.
Each school linked to its nearest tram stop. Across Lyon the median is 710 m, and 86 of 382 schools are within 300 m.

Two settings change the result:

  • Maximum distance. With 300 m, only 86 schools got a stop. The other 296 stayed in the output with empty fields, unless you tick Discard records which could not be joined.
  • Maximum nearest neighbors. With 3, the 382 schools became 1,146 rows, one per school and stop. An exact tie also adds a row: on the 103,910 IGN buildings we got 103,911 rows.

The distance is straight-line, not walking distance along streets.

Use it when: you ask "which is closest" and "how far".

Skip it when: you need travel distance. Use the Network analysis tools in the Processing Toolbox.

Which predicate to tick

The predicate is the spatial rule that decides a match. QGIS reads it as "layer A [predicate] layer B", where A is the layer that gets the fields.

Diagram of eight spatial relations between a red shape A and a blue shape B: intersect, they share any part; contain, A holds all of B; are within, A sits inside B; equal, same geometry; touch, they share an edge only; overlap, same type with part shared; cross, a line passes through; and no match, disjoint, nothing joined.
Red is the layer that gets the fields, blue is the layer you compare to.

Three rules cover most work:

  • intersect for points in polygons, and for anything that only needs to touch. It also catches features on the border.
  • are within when the feature must sit wholly inside. On our buildings, 112 that intersect Lyon were not wholly within one arrondissement: 108 cross the city limit and 4 an inner border.
  • contain for the reverse direction, polygons that hold points.

You can tick several boxes. A feature then matches if any of them is true.

One-to-one, one-to-many or largest overlap

A building on a border touches two districts. The Join type decides what happens to it. On the 46,250 IGN buildings that intersect Lyon:

Join typeRows outTime
One-to-many (a feature per match)46,2541.13 s
First matching feature only (one-to-one)46,2500.99 s
Largest overlap only (one-to-one)46,2509.62 s

One-to-many duplicated the 4 buildings that straddle an inner border. That is right for a list of matches and wrong for any sum: those buildings would be counted twice.

"First matching" keeps whichever match QGIS meets first, so a border building can land in either district. "Largest overlap" picks the district that holds most of it, which is the fair answer, but it took ten times longer here.

CRS pitfalls

A wrong coordinate reference system rarely stops a join. It silently changes the result.

  • Mixed CRS works for location joins. We left the schools in WGS 84 (EPSG:4326) and the districts in Lambert-93. QGIS reprojected on the fly and all 382 matched.
  • Distances follow the input layer's units. Run Join by nearest on the WGS 84 files and the median "distance" comes out as 0.0071. Those are degrees, not metres. Reproject both layers to a projected CRS in metres first, with Vector > Data Management Tools > Reproject Layer….
  • Maximum distance uses the same units. A 300 in degrees covers the whole planet.

Our guide on what a CRS is explains degrees versus metres and how to fix a layer in the wrong place.

Speed on large layers

QGIS handles city-scale joins in seconds. On the 103,910 IGN buildings:

  • One-to-one join to the 9 districts: 0.97 s from a GeoPackage.
  • The same layer as a Shapefile: 1.69 s. After Vector > Data Management Tools > Create Spatial Index…, 1.04 s.
  • Nearest tram stop for every building: 3.5 s.
  • Largest overlap: 9.62 s, the slowest option.

Four habits keep big joins fast:

  • Work from GeoPackage. It carries a spatial index. A Shapefile needs its .qix file, which Create Spatial Index writes.
  • Ask for fewer fields. Fill in Fields to add instead of copying everything.
  • Avoid largest overlap unless border features really matter.
  • Fix geometries first. An invalid polygon stops the run with "has invalid geometry". The Fix geometries tool in the Processing Toolbox repairs it. You can also change Invalid feature filtering under the wrench icon next to the input layer.

Check where your points sit, too. Only 211 of the 382 school points fell inside an IGN building. The rest mark playgrounds and yards, so "school inside building" would have missed 171 schools.

What to remember

Use Join Attributes by Location with intersect to give points the name of their area.

Use the Summary version to count or sum per area, and Join Attributes by Nearest for the closest feature and its distance.

Pick one-to-one for totals, and one-to-many when you need every match.

Reproject to a CRS in metres before any distance.

If you are not sure which tool fits, describe the result you want to AI Agent and check what it built.

Questions people ask

What is a spatial join in QGIS?

A spatial join adds the fields of one layer to another based on location: inside, touching or nearest. In QGIS the main tools are Join Attributes by Location, its Summary version for counts and statistics, and Join Attributes by Nearest for distances.

How do I join attributes by location in QGIS?

Open Vector > Data Management Tools > Join Attributes by Location…. Set the layer that gets the fields in Join to features in and the source layer in By comparing to, tick intersect, choose the fields and a one-to-one join type, then run it.

What is the difference between join by location and join by nearest?

Join by location needs the features to touch or overlap, under the predicate you tick. Join by nearest needs no contact: it finds the closest feature and adds a distance field, in the units of the input layer's CRS.

Why does my spatial join return empty fields?

Usually the features do not meet the predicate: points just outside the polygons, a predicate that is too strict such as "are within", or invalid geometries. Check both layers on the map, try "intersect", and run Fix Geometries. A missing CRS on one layer also misplaces it.

Can AI do a spatial join in QGIS?

Yes. AI Agent runs the QGIS tools from a typed request. On our Lyon test it did a count per district and a nearest join from one sentence in 22 seconds, with the same results as the manual run.

Try AI Agent free in QGIS, no card needed

Written byTerraLab
·13 min read
Contact