Spatial Join
Join layers spatially: points in polygons, nearest features
What Is a Spatial Join?
A spatial join is the operation of merging two spatial datasets based on a geometric relationship rather than a shared key field. Where an Attribute Join connects features by matching parcel_id = parcel_id, a spatial join connects them by asking "is this point inside this polygon?" or "which polygon contains this point?" or "which line is closest to this point?" The result is a new layer where each feature carries attributes from both source layers.
The spatial join is the second pillar of GIS analysis (the first being attribute filters). It is the foundation of operations like "how many crimes happened in each police precinct," "which voting precinct does each address belong to," "what is the watershed for each water quality monitoring site," and "what zoning category does each business location fall under." Without spatial joins, you would have to write custom point-in-polygon code for every analysis.
The gis.tools spatial join tool runs entirely in the browser using a fast R-tree spatial index to keep performance acceptable on large datasets. You drop two layers, pick the join type (one-to-one, one-to-many, many-to-many) and the spatial predicate (intersects, contains, within, nearest), and the tool produces a merged layer. No database, no command line, no spatial join geojson hand-coding.
For analysts coming from PostGIS, this is the same as ST_Intersects joins or ST_Contains joins. For ArcGIS users, this is Spatial Join in the analysis toolbox. For QGIS users, this is the Join Attributes by Location processing tool. The semantics are identical.
How Spatial Joins Work
Spatial index lookup
Naive spatial joins are O(N Γ M), checking every feature in one layer against every feature in the other. For two datasets of 10,000 features each, that is 100 million comparisons β slow even on modern hardware. The spatial join tool builds an R-tree index on the right (target) layer first. For each feature in the left (source) layer, it queries the index for candidates whose bounding boxes overlap, then runs the precise predicate only on those candidates. This reduces the typical complexity to O(N log M) and brings even million-feature joins into the seconds-to-minutes range.
Predicates supported
The spatial join supports the standard OGC Simple Features predicates:
- Intersects: any geometric overlap.
- Contains: the right feature entirely contains the left.
- Within: the left feature is entirely within the right.
- Touches: shared boundary, no interior overlap.
- Nearest: K-nearest right features (with optional max distance).
Cardinality handling
A single point may fall inside multiple overlapping polygons (rare but possible). A single polygon may contain many points (common). The join tool offers cardinality options:
- One-to-one: take the first match for each left feature; warn if there are multiple.
- One-to-many: emit one output row per match (inflates the output).
- Aggregate: count, sum, mean of right features per left feature (good for "how many points per polygon").
Aggregate functions
When the cardinality is many-to-one (many points per polygon), aggregate functions summarize the right-side attributes:
- count: number of matched right features
- sum: total of a numeric field
- mean: average of a numeric field
- min/max: extremes
- first/last: first or last matched value
- majority: most common categorical value
Key Parameters and Options
Join layers
Left (source) layer keeps its geometry. Right (target) layer contributes attributes.
Predicate
Intersects is the safe default for most queries. Use Within for strict containment. Use Nearest for proximity queries with no expected geometric overlap.
Cardinality and aggregation
Pick one-to-one for simple cases, one-to-many to expand the output, or aggregate to summarize.
Match limit
For nearest joins, specify K (number of nearest features) and an optional max distance.
Practical Applications
Crime by precinct
A crime analyst joins a year of incident points (10,000 records) to police precinct polygons (50 polygons). Using count aggregation, the result is the precinct layer with a incident_count attribute, ready to style with Graduated Color Styling for a precinct-level crime rate map.
Voter registration by district
An elections analyst joins voter registration addresses (geocoded to points) to legislative district boundaries to produce a registered voter count per district for redistricting analysis.
Wildlife observation in protected areas
A conservation biologist joins wildlife observation points to protected area polygons to count rare species sightings inside each park.
Property assessment by zoning
A city assessor joins parcel polygons to zoning polygons to assign each parcel its current zoning code for the assessment roll.
Customer-to-territory assignment
A sales operations team joins customer location points to sales territory polygons to assign each customer to a territory and a rep.
Weather to monitoring stations
An air quality scientist joins weather station points to a hex grid to interpolate weather conditions at each grid cell using nearest-neighbor matching.
Hazards to infrastructure
A risk analyst joins critical infrastructure points to FEMA flood zone polygons to identify which assets fall inside the 100-year floodplain.
Step-by-Step Workflow
- Load both layers (source and target) into the GeoJSON, KML, Shapefile & GIS File Viewer.
- Verify both layers are in the same CRS β use the EPSG Reprojector & Coordinate Converter if not.
- Open the spatial join tool.
- Pick the source layer (whose geometry will be retained).
- Pick the target layer (whose attributes will be attached).
- Choose a predicate (intersects, contains, within, nearest).
- Choose cardinality (one-to-one, one-to-many, aggregate).
- For aggregate joins, pick the function (count, sum, mean, etc.) and the field.
- Run the join β a new layer is produced.
- Inspect the output to verify match counts.
- Export with Multi-Select & Export.
Worked Example
A bicycle planner has a year of 14,800 reported road defect points and a layer of 1,200 street segments. They want to know which street segment each defect belongs to and how many defects each segment carries.
They load both layers, confirm both are in EPSG:4326, and open the spatial join tool. They pick the street segments as the source layer and the defects as the target. They choose the Nearest predicate with a max distance of 5 meters (because defect locations may be slightly off the centerline) and aggregate cardinality with count function.
The join runs in about 4 seconds. The output is the street segment layer with a new defect_count attribute. They style by defect_count using Graduated Color Styling and the resulting map immediately reveals two arterial corridors with 80+ defects each β clear priorities for the next round of repaving.
Common Pitfalls and Gotchas
- CRS mismatch. The most common spatial join failure. Always reproject to a common CRS first using the EPSG Reprojector & Coordinate Converter.
- Overlapping target polygons. Points falling on shared boundaries may match multiple targets. Use one-to-many cardinality to see all matches.
- Boundary inclusion ambiguity. A point exactly on a polygon edge may or may not be considered "within." Test with sample data.
- Slivers in the target layer. Sliver polygons from upstream operations can cause confusing joins.
- Nearest with no max distance. Without a distance cap, the nearest predicate matches the closest feature even if it is hundreds of kilometers away.
- One-to-many output explosion. Joining 10,000 points to 50,000 polygons with one-to-many can produce hundreds of thousands of output rows.
- Aggregate on missing field. Sum/mean on a non-numeric or null field returns nonsense.
- Memory pressure. Loading two large layers and producing a third can exhaust browser RAM.
Tips for Best Results
- Always reproject both layers to the same CRS first.
- Use Intersects unless you have a specific reason to use Within or Contains.
- Use aggregate joins (count, sum) when you want polygon-level summaries.
- Use nearest joins with max distance for proximity matching.
- Validate input geometries with the GeoJSON Validator & Fixer before joining.
- Test on a small subset before running on the full dataset.
- Save the join configuration for reproducibility.
- Inspect the match count in the join preview before committing.
Comparison with PostGIS, QGIS, and ArcGIS
PostGIS implements spatial joins as standard SQL joins with ST_Intersects, ST_Contains, and ST_DWithin in the join condition. The spatial index is GiST. QGIS calls this Join Attributes by Location and offers a similar set of predicates and aggregation options. ArcGIS Spatial Join is part of the analysis toolbox.
The gis.tools approach is identical in semantics but runs entirely in the browser. The trade-off is scale: PostGIS handles billion-row joins, the browser tops out at low millions. For very large workloads, do the join in a database and load only the result. For everything else β most one-off analysis tasks β the browser-based tool is faster end-to-end because there is no database to set up.
Performance Considerations
With an R-tree index, joining 10,000 points to 1,000 polygons takes under a second. 100,000 to 10,000 takes a few seconds. 1 million to 100,000 may take 30-60 seconds depending on geometry complexity. RAM is usually the first constraint at the high end.
Data Privacy and Browser-Based Processing
Spatial joins run entirely in your browser. Both input layers and the output stay on your machine. This makes the tool suitable for sensitive workflows like joining patient addresses to hospital service areas, combining witness locations with police beats, or assigning confidential customers to sales territories.
Related GIS Concepts
Point-in-polygon. The classic geometric test underlying many spatial joins.
R-tree spatial index. The data structure that makes spatial joins fast.
OGC Simple Features. The specification that defines the spatial predicates.
Aggregate function. SQL-style summary functions (count, sum, mean) applied to grouped rows.
Many-to-many cardinality. When both sides have multiple matches per row.
Frequently Asked Questions
What is the difference between a spatial join and an attribute join?
Attribute join matches by a shared key column (Join Tool). Spatial join matches by geometric relationship.
How do I count points in polygons?
Use a spatial join with intersects predicate and count aggregation.
What CRS should I use?
For accuracy, use a local projected CRS (UTM zone). For convenience, use WGS84 with intersects (which works fine for most queries).
Can I join lines to polygons?
Yes. Use intersects or within depending on whether you want partial or full containment.
How do I find the nearest feature?
Use the Nearest predicate with K=1 and an optional max distance.
Related Tools on gis.tools
Related Tools
View All ToolsDe-duplication Tool
Find and remove duplicate features by coordinates or key
Query & FilterColumn Statistics
View distinct values, null counts, and histograms for fields
Query & FilterFeature Identify Tool
Click features to view attributes in a popup
Query & FilterSpatial Filter
Filter features within viewport or a drawn polygon
Query & FilterJoin Tool (CSV β Layer)
Join CSV data to a layer by a common key field
Query & FilterData Profiling Report
Generate schema analysis and data quality flags
Query & Filter100% client-side processing - your data stays private and never leaves your device