Skip to content

RomneyDa/mapthis

Repository files navigation

@dromney/mapthis

Open-source location intelligence toolkit. Parse freeform text, HTML, and URLs into geocoded locations; ship with types and React components for rendering maps.

npm install @dromney/mapthis

You'll also want one or more optional peer deps (see Subpath exports):

# pick what you use:
npm install openai js-tiktoken                       # @dromney/mapthis/ai (OpenAI)
npm install @anthropic-ai/sdk js-tiktoken            # @dromney/mapthis/ai (Anthropic)
npm install @googlemaps/google-maps-services-js      # @dromney/mapthis/geocoding
npm install html-to-text                             # @dromney/mapthis/scrape
npm install react react-dom @vis.gl/react-google-maps  # @dromney/mapthis/react

Quick start

import { createLocationParser } from "@dromney/mapthis/ai"
import { createGeocoder } from "@dromney/mapthis/geocoding"
import { createMapGenerator } from "@dromney/mapthis/generate"

const generator = createMapGenerator({
    parser: createLocationParser({ apiKey: process.env.OPENAI_API_KEY! }),
    geocoder: createGeocoder({ provider: "google", apiKey: process.env.GOOGLE_MAPS_KEY! }),
})

const result = await generator.generateFromSource({
    sourceType: "url",
    source: "https://example.com/best-restaurants-tokyo",
})

console.log(result.title)
for (const p of result.places) {
    if (p.data) console.log(p.data.name, p.data.lat, p.data.lng)
    else console.warn("could not geocode", p.input.address, "—", p.error)
}

sourceType accepts "url", "text", or "list". For "list", each newline-separated entry is geocoded directly with no LLM call (and no LLM cost).

Subpath exports

@dromney/mapthis ships a root entry plus focused subpaths so consumers only load what they use.

Subpath What it contains Optional peer deps
@dromney/mapthis Re-exports types + utils (lightweight only)
@dromney/mapthis/types Zod schemas + TS domain types (PlaceMap, Place, PlaceGroup, Partner, ParsedLocation, Coordinates, …)
@dromney/mapthis/utils Pure helpers: text, color, numbers, geo, stopwatch
@dromney/mapthis/scrape getHtmlFromUrl, htmlToText, getTextFromUrl html-to-text
@dromney/mapthis/search createSearchClient (Google Custom Search)
@dromney/mapthis/ai createLocationParser, createOpenAiBackend, createAnthropicBackend, summarizeText, prompts openai or @anthropic-ai/sdk, plus js-tiktoken
@dromney/mapthis/geocoding createGeocoder (Google Maps) with pluggable GeocodingProvider @googlemaps/google-maps-services-js
@dromney/mapthis/generate createMapGenerator — composes parser + geocoder into a pure orchestrator — (transitively whatever parser/geocoder use)
@dromney/mapthis/react MapProvider, GoogleMapsViewer, PlaceMarker, autofit, browserAutocomplete react, react-dom, @vis.gl/react-google-maps

Choosing an LLM backend

createLocationParser accepts either an OpenAI config, an Anthropic config, or a bring-your-own backend implementing the LlmBackend interface.

// OpenAI (default)
createLocationParser({ apiKey: process.env.OPENAI_API_KEY! })
createLocationParser({ provider: "openai", apiKey: ..., model: "gpt-4o-mini" })

// Anthropic
createLocationParser({
    provider: "anthropic",
    apiKey: process.env.ANTHROPIC_API_KEY!,
    model: "claude-haiku-4-5-20251001",
})

// Bring-your-own (e.g. an Azure/Bedrock adapter or a deterministic test fake)
createLocationParser({ backend: myCustomBackend })

Both bundled backends implement the same two-stage pipeline: a chunked summarization pass to fit the model's context window, followed by a structured-output call (OpenAI function calling, Anthropic tool use) to extract the location list.

Persisting results

createMapGenerator is intentionally pure — it returns a plain object and never touches a database. Wire the result into your own persistence layer:

const result = await generator.generateFromSource({ sourceType: "url", source: url })

await db.$transaction(async (tx) => {
    const map = await tx.map.create({ data: { title: result.title /* … */ } })
    const group = await tx.placeGroup.create({ data: { mapId: map.id /* … */ } })
    await tx.place.createMany({
        data: result.places.map((p, i) => ({
            mapId: map.id,
            groupId: group.id,
            position: i,
            query: p.input.address,
            description: p.input.description,
            name: p.data?.name,
            address: p.data?.address,
            lat: p.data?.lat,
            lon: p.data?.lng,
            provider: p.data?.provider,
            providerId: p.data?.providerId,
            error: p.error,
            locationPromptVersion: result.locationPromptVersion,
            summaryPromptVersion: result.summaryPromptVersion,
        })),
    })
})

Error handling

Every error thrown by the package extends MapthisError (@dromney/mapthis/types). Domain-specific subclasses let callers handle the failure modes that matter to their UX:

Class Subpath When
InvalidUrlError, ScrapeError /scrape URL was unreachable or returned non-HTML
NoSearchResultsError, SearchError /search Google CSE returned zero usable results
NoLocationsFoundError, AiInputLengthError, AiOutputLengthError, AiResponseJsonError, InvalidJsonSchemaError, SummarizeTextError, AiError /ai LLM stage failed
GeocodingError /geocoding Geocoder threw (per-input failures resolve, not throw)

Per-input geocoder failures are not thrown — geocodeMany returns a PlaceQueryResult with data: null and error: string so one bad address can't fail an entire batch.

Design principles

  • No process.env reads. All secrets are passed to factory functions by the consumer. Keeps the package portable and testable.
  • Framework-agnostic. Core modules are pure TypeScript. React components live behind the @dromney/mapthis/react subpath so server-only consumers skip them entirely.
  • Plain domain types. Hand-rolled TS with matching Zod schemas. Consumers aren't locked to a particular ORM — write adapter functions between your DB models and these types in your app.
  • Optional peer deps for heavy things. Every SDK is an optional peer dependency. Install only what the subpaths you use need.

License

Apache 2.0 — see LICENSE.

About

A collection of AI tools and prompts for converting freeform data into geolocations

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors