Geo Plotting
Kandy-Geo adds extensions which allow plotting a GeoDataFrame
— a DataFrame-based structure for geospatial datasets. All the plotting principles and features here are the same as Kandy, the only difference is that you don't have to perform positional mapping — instead geometries will be automatically mapped to Kandy layers.
Kandy-Geo and DataFrame-Geo Usage
To integrate Kandy-Geo and DataFrame-Geo into an interactive notebook, use the following commands:
To include Kandy-Geo and DataFrame-Geo in your Gradle project, add the following to your dependencies:
Geometries
Geo plotting is essentially the visualization of geographic geometries on a map or coordinate system. GeoJSON is the most widely used standard for representing geospatial data. It defines a set of geometry types that are simple yet powerful for modeling geographic features:
Point
: Represents a specific location as a single coordinate.MultiPoint
: A collection of multiplePoint
geometries.LineString
: A sequence of connected points, forming a path or linear feature.MultiLineString
: A collection of multipleLineString
geometries.Polygon
: A closed shape with an outer boundary and optional inner holes.MultiPolygon
: A collection of multiplePolygon
geometries.GeometryCollection
: A container for any combination of the above geometries.
JTS (Java Topology Suite) is a library that works seamlessly with these geometries, adding a variety of operations. It allows you to perform tasks like combining geometries, finding intersections, creating buffers, or simplifying shapes.
All classes for the aforementioned geometries are provided in JTS and inherit from the base class Geometry
. GeoDataFrame
is a wrapper around a standard DataFrame
with a geometry
column of type Geometry
, enabling convenient handling of geospatial datasets.
Reading GeoDataFrame
Currently, the GeoDataFrame supports two of the most popular formats: Shapefile and GeoJSON. These formats can be read into a GeoDataFrame
using the corresponding GeoDataFrame.read..()
functions. Each of these functions returns a GeoDataFrame
.
GeoJSON
GeoJSON is a widely used format for encoding geographic data structures. It represents spatial features such as points, lines, and polygons, along with their properties, using JSON. Here's an example of GeoJSON:
Let's load a GeoJSON file that contains polygons representing the boundaries of US states:
We can directly access the underlying DataFrame to take a closer look at its contents:

This DataFrame is required to have a geometry column of type org.locationtech.jts.geom.Geometry
:
Output:
We can also check the exact types of these geometries:
Output:
As expected, these are Polygon
and MultiPolygon
.
The GeoDataFrame
also contains a .crs
field for the coordinate reference system (CRS). In GeoJSON, this field is not explicitly defined* and is read as null
. If this field is not explicitly set in the GeoDataFrame, it is assumed by default to use WGS84 — the standard CRS for working with geospatial data.
* According to the GeoJSON specification, all coordinates are defined in WGS84. In the future, we may remove the nullability of the crs
field, and WGS84 will be explicitly set as the CRS when reading GeoJSON files.
Output:
Shapefile
Shapefile is a popular geospatial vector data format developed by ESRI. It stores geometric features such as points, lines, and polygons, along with their attributes, across multiple files. A Shapefile requires at least three parts: .shp
(geometry), .shx
(spatial index), and .dbf
(attributes), and it typically uses a defined coordinate reference system.
To load a Shapefile, you need to specify the path to the file with the .shp
extension. The other required files must be in the same directory and share the same base name.
Let's load a Shapefile with the most populated cities in the world:
Take a look inside the DataFrame:

This GeoDataFrame
contains only Point
geometries:
Output:
And has explicitly specified CRS:
Output:
Plot
Geo plotting in Kandy is not significantly different from usual plotting. The main distinction is that you need to provide the aforementioned geometries instead of specifying positional mappings.
To facilitate this, Kandy-Geo introduces geo layers, which, unlike regular layers, accept geometries. These can be provided as DataFrame columns, Iterable
, or single instances. If a layer is built in the context of a GeoDataFrame
dataset, it is not necessary to explicitly specify the geometry, as the geometry
column will be used by default.
geoPolygon
The geoPolygon()
adds a layer of polygons constructed using Polygon
and MultiPolygon
geometries.
Let's plot US states from usaStates
:
The customization process for such a layer is no different from a regular one. The function optionally opens a block where you can configure all polygon aesthetic attributes as usual using mappings and settings. For example, you can color each state by mapping the name
column to fillColor
and customize the borderLine
as shown below:
Mercator coordinates transformation
The Mercator projection is widely used for map visualizations because it preserves angles and shapes locally, making it ideal for navigation and geographical applications. It is particularly useful for rendering maps on flat surfaces, such as screens or paper. The Mercator projection is compatible with coordinates in the WGS84 coordinate system, as it uses latitude and longitude values to project the curved surface of the Earth onto a 2D plane. In this projection, only the axes of the plot are transformed, while the actual values of the points remain unchanged.
geoMap
geoMap()
is a basically geoPolygon()
but it also applies coordinates transformation based on the provided CoordinateReferenceSystem
(GeoDataFrame.crs
). Now only WGS84 is supported (where the mercator projection is applied by default).
When the Mercator projection is applied, we can still set axis limits as usual. However, there are inherent boundaries at 180 and 90 degrees due to the nature of geographic coordinates.
geoPoints
The geoPoints()
adds a layer of points constructed using Point
and MultiPoint
geometries.
Let's add worldCities
points over usaStates
polygons:
GeoDataFrame modifying
Before plotting, it is often necessary to modify the geo- dataframe. For example, you might filter points within a specific area, translate or scale certain geometries, and so on. GeoDataFrame
allows direct updates to its inner DataFrame
using the familiar DataFrame Operations API.
DataFrame operations
The function GeoDataFrame<T>.modify(block: DataFrame<T>.() -> DataFrame<T>): GeoDataFrame<T>
opens a new scope where the receiver is the inner DataFrame
of this GeoDataFrame. This allows you to perform operations such as filter
, take
, sort
, update
, and others directly on it. The function returns a GeoDataFrame with the modified DataFrame resulting from the block, while keeping the CRS unchanged.
Let's filter the points in worldCities
, keeping only those located within the US. To do this, we will first combine all polygons from usaStates
into a single polygon for convenience:
Now, let's create a GeoDataFrame usaCities
containing only the cities located within the United States. To avoid over plotting, we will select the 30 most populous cities. For this, we will modify worldCities
:
Now we can visualize the result by overlaying the points representing these cities on the polygons of the states (as above):
As you can see, the map of the US is significantly stretched by distant territories such as Puerto Rico, Hawaii, and Alaska. We can remove these regions, keeping only the continental part (48 states):
Geometry operations
Another, more elegant way to improve the appearance of the US map is to scale and reposition these polygons, making the plot more compact.
The DataFrame-Geo library provides Kotlin-style extensions for JTS geometries. For instance, Geometry.translate(x, y)
shifts a geometry by a specified vector, while Geometry.scaleAroundCenter(factor)
scales a geometry relative to its centroid.
An example of the states maps with their centroids:
Datasets Join
In geo-plotting, separate datasets are often used—one containing the geometries and others with specific data. To combine them, you can join them using modify
. Let's load a DataFrame
with the results of the 2024 US presidential election:

And join it to the US states GeoDataFrame
:

Now we can create a geo plot with a color scale based on state election results:
Applying new CRS
A new coordinate system can be applied to a GeoDataFrame by projecting all geometries into it (note that this is not always possible, so proceed with caution).
The CONUS (Conterminous United States) Albers projection is a widely used coordinate reference system tailored for the contiguous United States (48 states). It is an equal-area projection, meaning it preserves area proportions while slightly distorting shapes and distances. This projection is ideal for visualizing geographic features across large regions of the continental US.
Let's apply the CONUS Albers projection to the state polygons:
Output :
geoPath
The geoPath()
adds a layer of a path constructed using LineString
and MultiLineString
geometries.
The following function constructs the shortest path on the Earth's surface, known as a great-circle line. A great-circle line represents the shortest distance between two points on a sphere, following the curvature of the Earth. The path is approximated using a LineString
with a specified number of points n
for precision.
This convenient function finds a city in usaCities
by name and returns its geometry (point):
Use it to take points of New York and Los Angeles:
Count the shortest path between them:
Now, let's plot this curve using geoPath
, overlaying it on top of the state polygons and highlighting the points corresponding to the cities:
geoRectangles
The geoRectangles()
adds a layer of rectangles constructed using Envelope
. The Envelope
class represents a rectangular region in the coordinate space, defined by its minimum and maximum coordinates. It is commonly used for bounding boxes, spatial indexing, and efficient geometric calculations.
Let's get usa48
common bounding box:
And plot it with the polygon plot:
In addition, geoRectangles
also works with polygons and multipolygon. In such cases, the bounding box of each geometry will be calculated and used individually:
Write GeoDataFrame
A GeoDataFrame
can be saved to a file in both GeoJSON and Shapefile formats using the GeoDataFrame.write..(filename)
functions.
GeoJSON
Let's save the modified GeoDataFrame containing US cities, which was initially in Shapefile format, to a GeoJSON file.
Shapefile
Unlike GeoJSON, Shapefile supports only one type of geometry.
Let's save the GeoDataFrame containing the boundaries of US states, which was initially in GeoJSON format and included both polygons and multipolygons, to a Shapefile. To do this, we will first cast all geometries to MultiPolygon
.