Most astrocartography tools online are either locked behind a $20/month paywall, built on Flash-era technology, or so cluttered with ads you can barely find the actual calculator. As a developer who stumbled into the world of astrology charts, I kept thinking: someone should just build a clean, free one.
So I did. Here's the honest story of how I built Astrocarto — the technical decisions, the unexpected hard parts, and what I'd do differently.
What Even Is Astrocartography?
Quick background for the non-astrology crowd (which is probably most of you):
Astrocartography is a technique that takes the positions of planets at the exact moment you were born and projects them as lines onto a world map. The idea is that certain locations carry specific planetary energies for you — your "Venus line" might run through Paris, your "Mars line" through somewhere you'd rather avoid.
Whether or not you believe any of that — the math is genuinely fascinating. We're talking about:
- Calculating precise planetary positions using historical ephemeris data
- Transforming between multiple astronomical coordinate systems
- Projecting those coordinates onto a 2D geographic map
As a developer, I was hooked on the problem before I even thought about the product.
The Tech Stack
Framework: Next.js 14 (App Router)
Styling: Tailwind CSS
Map Rendering: Leaflet + React-Leaflet
Astro Calc: astronomia (npm)
Deployment: Vercel
I'll explain the key choices below.
Why Next.js?
Three reasons:
1. SEO is everything for a niche tool
People actively search for "free astrocartography calculator" — I needed server-side rendering so those pages could rank. With Next.js App Router, my calculator pages are fully rendered on the server before they hit the browser.
2. Route Handlers for calculation logic
I didn't want heavy astronomical math running on the client. Next.js Route Handlers let me keep that on the server cleanly:
// app/api/calculate/route.js
export const runtime = 'nodejs'; // critical — don't use Edge here
export async function POST(request) {
const { birthDate, birthTime, lat, lng } = await request.json();
const chart = calculateNatalChart({ birthDate, birthTime, lat, lng });
return Response.json({ chart });
}
3. Zero-config Vercel deployment
Push to main → live in 30 seconds. For an indie project, this matters.
The Hardest Part: Astronomical Calculations
I expected the map to be the hard part. I was wrong. The calculations nearly broke me.
Using the astronomia library
astronomia is a JavaScript port of Jean Meeus's Astronomical Algorithms — the gold standard reference for this kind of math. It handles the heavy lifting of planetary position calculations.
Here's a simplified version of how I get a planet's ecliptic longitude:
import { solar, julian, base } from 'astronomia';
function getPlanetPosition(birthDate, birthTime) {
// Convert birth datetime to Julian Day Number
const dateStr = `${birthDate}T${birthTime}:00Z`;
const jd = julian.DateToJD(new Date(dateStr));
// Get Sun's position (as example)
const sunPos = solar.apparentLongitude(jd);
// Convert from radians to degrees
const longitude = base.pmod(sunPos * 180 / Math.PI, 360);
return longitude;
}
The coordinate system nightmare
Where it got painful: astrocartography lines require converting between three coordinate systems:
- Ecliptic coordinates — where planets are relative to Earth's orbital plane
- Equatorial coordinates — right ascension + declination (like lat/lng for space)
- Geographic coordinates — actual latitude/longitude on Earth's surface
For each planet, I need to find every latitude on Earth where that planet was on the Ascendant, Descendant, Midheaven, or IC at the moment of birth. That means iterating through latitudes and solving for the longitude where the condition holds.
function findAscendantLine(planet, jd) {
const points = [];
// Step through every latitude
for (let lat = -80; lat <= 80; lat += 0.5) {
// Binary search for the longitude where this planet rises
const lng = solveForAscendant(planet, jd, lat);
if (lng !== null) {
points.push([lat, lng]);
}
}
return points;
}
Getting this right took me about two weeks of head-scratching, cross-referencing with known charts, and fixing edge cases near the poles.
Rendering the Map with Leaflet
Once I had the coordinate arrays, rendering them with Leaflet was comparatively straightforward:
import { MapContainer, TileLayer, Polyline } from 'react-leaflet';
export function AstroMap({ planetLines }) {
return (
<MapContainer
center={[20, 0]}
zoom={2}
style={{ height: '600px', width: '100%' }}
>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution="© OpenStreetMap contributors"
/>
{planetLines.map(({ planet, points, color }) => (
<Polyline
key={planet}
positions={points}
pathOptions={{ color, weight: 2, opacity: 0.8 }}
/>
))}
</MapContainer>
);
}
One Leaflet gotcha with Next.js: Leaflet touches window on import, which breaks SSR. Fix:
// Use dynamic import with ssr: false
const AstroMap = dynamic(() => import('@/components/AstroMap'), {
ssr: false,
loading: () => <div className="map-skeleton" />
});
What Astrocarto Can Do Right Now
The tool currently supports:
- ✅ Rising Sign Calculator — your ascendant based on exact birth time + location
- ✅ Natal Chart — full planetary positions at birth
- ✅ Transit Chart — current planetary positions overlaid on your natal chart
- ✅ Relocation Chart — how your chart shifts if you move cities
All free. No account required. Just enter your birth details and go.
Try it: astrocarto.org
What I'd Do Differently
1. Validate calculations against existing tools first
I spent days debugging math that turned out to be correct — I just didn't have a reliable reference to check against. Next time I'd set up automated comparison tests against established chart software from day one.
2. Build the map UI before perfecting the calculations
Seeing lines on a map, even approximate ones, keeps you motivated. I got too deep into calculation accuracy before I had anything visual to show for it.
3. Add caching earlier
Astronomical calculations for the same birth data always return the same result. I should have added a simple cache from the start rather than recalculating on every request.
// Simple in-memory cache I added later
const cache = new Map();
export async function POST(request) {
const body = await request.json();
const cacheKey = JSON.stringify(body);
if (cache.has(cacheKey)) {
return Response.json(cache.get(cacheKey));
}
const result = calculateNatalChart(body);
cache.set(cacheKey, result);
return Response.json(result);
}
What's Next
- [ ] Mobile-optimized map gestures
- [ ] Shareable chart URLs (so you can send your chart to a friend)
- [ ] PDF export
- [ ] More celestial bodies — asteroids, Chiron, nodes
Building niche tools is genuinely one of my favorite things to do as an indie developer. The intersection of an obscure domain and a real technical problem is where the most interesting projects live.
If you've built something in a weird niche — astrology, birdwatching, chess, anything — I'd love to hear about it in the comments.
And if you're curious about your own astrocartography: astrocarto.org — takes about 60 seconds.

Top comments (1)
Just published this after sitting on the draft for a while — the coordinate system conversion was genuinely the most humbling part of this whole project 😅
If you've ever tried to mix astronomical math with web dev, I'd love to compare notes. And if you give astrocarto.org a try, let me know what you think — always looking for feedback from fresh eyes.