Central de Inteligência (TerraLogs)

Central de Inteligência (TerraLogs)

A geospatial intelligence platform where the hard engineering is entirely in the browser — an object-oriented layer engine over Google Maps.

TerraLogs turns large geospatial datasets into an interactive map: rural properties, territories, environmental areas and mining processes, drilled from national scope down to a single município. This is a front-end-only role, which usually means the interesting problems live elsewhere — here they did not. Rendering thousands of features across five rendering strategies, keeping them consistent while filters change faster than requests resolve, and doing it without freezing the tab, needed a real architecture. I own that engine; a second engineer works alongside me on the product.

ROLE

Front-End Engineer

Map Architecture

TOOLS

Vue 3

Quasar

TypeScript

TanStack Query

Google Maps API

Supercluster

Turf.js

ECharts

DURATION

2025 - Present

INTEGRATIONS

Google Maps

AWS Cognito (OIDC)

OpenAPI REST backend

✶ An OO layer engine, not a pile of map callbacks

4.4k lines of pure TypeScript under src/core, with no Vue in it. Layers are declared as data — including their click handlers, as typed string keys into a registry — and a factory picks one of five implementations behind a single ILayerType contract. Adding a rendering strategy is a new subclass and one switch case.

✶ GeoJSON: eight stages between the API and a click

The pipeline exists because the naive version fails quietly. Google's Data layer fires no events on a GeometryCollection, so every one is expanded into standalone features that keep the parent's id and properties — otherwise clicks simply stop working, with no error anywhere to point at.

Raw response → clickable map layer

01

raw response

three shapes from the same API

feature vs features · features:[[…]] double-nested · paginated

02

normalize + accumulate

page 1 reveals totalPages, the rest fetched in parallel

callers never learn the endpoint was paginated

03

Object.freeze(geojson)

before it ever reaches reactive state

stops Vue building deep proxies over thousands of features

04

expand GeometryCollection

each inner geometry becomes its own Feature, keeping parent id + properties

google.maps.Data fires no events on a GeometryCollection — clicks would die silently

05

local filters

supply chain · titular · fase do processo

accent-folded, and digs through three possible property shapes

06

dejitter collocated points

bucket by 6-decimal key, spiral the rest onto 8-slot rings at ~9 m

stable across reloads by id sort — pins stop hiding under each other

07

style closure per feature

selection highlight · point vs polygon · opportunity icon swap

updateSelectedId() repaints without re-adding a single feature

08

google.maps.Data

hit-testing gated to Point / MultiPoint on territory layers

otherwise the polygon fill swallows every pin click inside it

Every stage exists because something failed without it. The three highlighted ones are the non-obvious ones — they look like they should not be necessary until the map goes quiet or the tab freezes.

✶ Filters change faster than requests resolve

Changing município mid-flight aborts the old request, but aborting is not enough — the response can still arrive. Every payload is revalidated against the current filters after the await and discarded if the scope moved, with the same guard mirrored on the error path so a stale 404 never surfaces over data you are actually looking at.

Why the map never paints a filter you already left

user

picks município A

UrlResolver produces url A · fetch starts

user

changes to município B

300 ms later — the A request is still in flight

code

url changed → abort

and resetLayers() runs immediately, before the async abort handler

code

fetch A resolves anyway

abort is not instantaneous — the response still arrives

code

re-resolve url against current filters

A ≠ B → discard the payload, never paint it

code

same guard on the error path

a stale 404 for A never raises a toast while you are looking at B

code

cleanup only if still current

the finally block compares controller identity before clearing the map entry

Aborting is not enough on its own: the response can still land, and the naive cleanup can clobber a newer request. The identity being compared is the resolved URL, which is also what the cache is keyed on.

✶ Selection rules as a pure function

Five interacting rules — single, special, drill-down, exclusive, default — resolved in fixed precedence by toggle(id, activeSet) → Set. No map, no network, no framework, including the inverted case where deactivating a parent activates its children. Pure by design, so the behaviour stays testable instead of scattering across components.

✶ Keeping the main thread alive

GeoJSON is frozen before it reaches reactive state so Vue never builds deep proxies over thousands of features; every Google object is markRaw'd; clustering culls to the viewport and re-renders only on idle; and switching visualization modes yields to the browser between teardown steps rather than blocking straight through them.

✶ Two domains, one engine

The same core drives AGRO — verified and estimated properties, irrigation pivots, indigenous and quilombola territories, protected areas — and MineraLogs, over ANM mining processes with phase, holder and opportunity ranking. They differ by configuration, not by code.