npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

tiles-math

v1.4.0

Published

Algorithms and data structures for mapping geo coordinates to slippy-map tiles, clustering of adjacent tiles, and finding largest squares included in clusters

Downloads

88

Readme

Tiles Math

This library provides algorithms and data structures to map your rides and runs to slippy map tiles. It then groups the tiles in various ways, similar to what VeloViewer, StatsHunters, Squadrats, and RideEveryTile do.

Specifically, tiles-math

  • maps geo positions (latitude, longitude) to map tiles of a given zoom level,
  • combines adjacent tiles to connected clusters,
  • selects the maximum-size tile cluster,
  • computes the boundary polylines of tile clusters,
  • detects the largest and most central square included in a cluster,
  • finds all maximum-size rectangles embedded in a cluster,
  • calculates the map coordinates of tiles, clusters, polylines, squares, rectangles.

The library does not provide GPX parsing routines. It also does not come with map functionalities, but it can be easily integrated into map frameworks such as Leaflet. In particular, the map coordinates calculated for the various artifacts are compatible with Leaflet's LatLng and LatLngBounds types, and can thus directly used for drawing Leaflet Rectangles, Polylines, and similar map overlays.

The demo folder shows an example of applying tiles-math to a React Leaflet map. Red squares represent tiles of zoom level 14 touched by (fake) rides, purple regions show smaller clusters (i.e. tiles with four neighbors), the blue area depicts the maximum cluster (with the orange circle as its centroid), and the yellow frame shows the maximum square:

Because tiles-math is a pure Javascript library (written in Typescript) without dependencies and without reference to a specific map framework, it can be used in the browser and in Node servers. The latter is very useful to pre-compute all tiles for all your rides and runs, and then deliver the resulting tile set via API to the browser.

Installation

npm install tiles-math

Usage

Display a Tile Set created from GPS Positions

Assume you have a number of tracks stored e.g. as GPX files. With function coords2tile you find the tile touched by a latitude-longitude pair, given a zoom level (often 14). Class TileSet holds all unique (non-duplicate) tiles of all your rides and runs. Note that you can compute the tile set on the backend, and deliver them via API.

import { Rectangle } from 'react-leaflet'
import { coords2tile, TileSet } from 'tiles-math'

const zoom = 14 // VeloViewer and others use zoom-level 14 tiles
const coords = [[51.492084, 0.010122], ...] // The latitude-longitude pairs or your rides
const tiles = new TileSet().addAll(coords.map(latLon => coords2tile(latLon, zoom)))

export const TileContainer = () => (
    <div>
        {tiles.map((tile, index) => (
            <Rectangle key={index} bounds={tile.bounds(zoom)} pathOptions={{ color: 'red' }} />
        ))}
    </div>
)

The complete source code can be found in DemoTrackTiles.tsx.

Clustering of a Tile Set

Tiles with a neighboring tile on each side belong to a cluster. A tile set can have multiple clusters. The largest cluster is called max cluster. The following code snipped takes a tile set as input and computes the max cluster, all minor clusters, and the remaining set of detached tiles:

import { Rectangle } from 'react-leaflet'
import { tiles2clusters, TileSet } from 'tiles-math'

const zoom = 14
const tiles = new TileSet().addAll([...]) // The input tile set 
const { detachedTiles, minorClusters, maxCluster } = tiles2clusters(tiles)

export const TileContainer = () => (
    <div>
        <>
            {detachedTiles.map((tile, index) => (
                <Rectangle key={index} bounds={tile.bounds(tileZoom)} pathOptions={{ color: 'red', weight: 0.5 }} />
            ))}
        </>
        <>
            {minorClusters.map((tile, index) => (
                <Rectangle key={index} bounds={tile.bounds(tileZoom)} pathOptions={{ color: 'purple', weight: 1 }} />
            ))}
        </>
        <>
            {maxCluster.map((tile, index) => (
                <Rectangle key={index} bounds={tile.bounds(tileZoom)} pathOptions={{ color: 'blue', weight: 2 }} />
            ))}
        </>
    </div>
)

For more information see DemoClustering.tsx.

Show the Boundaries of a Tile Cluster

You may want to highlight the boundaries of a cluster on the map. Note that a cluster may have an outer boundary, and multiple inner boundary polylines for cut-out areas.

import { Polyline, Rectangle } from 'react-leaflet'
import { cluster2boundaries, tiles2clusters, TileSet } from 'tiles-math'

const zoom = 14
const tiles = new TileSet().addAll([...]) // The input tile set 
const { maxCluster } = tiles2clusters(tiles)
const boundaries = cluster2boundaries(maxCluster)

export const TileContainer = () => (
    <div>
        <>
            {maxCluster.map((tile, index) => (
                <Rectangle key={index} bounds={tile.bounds(tileZoom)} pathOptions={{ color: 'blue', weight: 0.5 }} />
            ))}
        </>
        <>
            {boundaries.map((line, index) => (
                <Polyline key={index} positions={line.positions(tileZoom)} pathOptions={{ color: 'blue', weight: 4 }} />
            ))}
        </>
    </div>
)

The source code can be found in DemoBoundaries.tsx.

Find the Max Square embedded in a Tile Cluster

The max square is the square with the maximum edge length embeddable into the max cluster. There may be several max squares in a cluster. Function cluster2square selects the max square that is closest to the centroid of the max cluster.

import { Rectangle } from 'react-leaflet'
import { cluster2square, tiles2clusters, TileSet } from 'tiles-math'

const zoom = 14
const tiles = new TileSet().addAll([...]) // The input tile set 
const { maxCluster } = tiles2clusters(tiles)
const maxSquare = cluster2square(maxCluster).getCenterSquare()

export const TileContainer = () => (
    <div>
        <>
            {maxCluster.map((tile, index) => (
                <Rectangle key={index} bounds={tile.bounds(tileZoom)} pathOptions={{ color: 'blue', weight: 0.5 }} />
            ))}
        </>
        <>
            {maxSquare &&
                <Rectangle bounds={maxSquare.bounds(tileZoom)} pathOptions={{ fill: false, color: 'yellow', weight: 4 }} />
            }
        </>
    </div>
)

See DemoMaxSquare.tsx for the complete source code.