DEV Community

KevinTen
KevinTen

Posted on

From Zero to Hero: Building a Spatial Memory API That Actually Survives Real-World Testing

From Zero to Hero: Building a Spatial Memory API That Actually Survives Real-World Testing

Honestly, when I started building spatial-memory, I thought I was creating the next big thing in AR. I mean, who wouldn't want to pin multimedia memories to real-world locations, right? The idea sounded straight out of a sci-fi movie - walking down the street and suddenly seeing photos pop up exactly where they were taken.

But here's the thing: after 6 months of intense development, I've learned that the gap between "cool idea" and "actually works" is bigger than the Grand Canyon. And today, I want to take you through that journey - the brutal truths, the unexpected lessons, and the code that actually made it work.

The Dream vs. The Reality: What I Thought vs. What I Got

My Initial Fantasy (Month 1)

I imagined users lovingly pinning memories to locations, creating beautiful digital scrapbooks of their lives. The app would be smooth, the AR rendering would be flawless, and users would be uploading thousands of memories every day.

The Brutal Truth (Month 6)

I ended up with:

  • 20 real users (mostly friends and family feeling sorry for me)
  • GPS accuracy nightmares (3-5 meters in ideal conditions, 20-30 meters in cities)
  • Battery drain issues that would make even power banks cry
  • AR rendering nightmares on different devices
  • Zero revenue and a -100% ROI

Technical Architecture: What Actually Worked

The Core Data Structure

Here's the thing about GPS data - it's nowhere near as precise as you think. My first attempt used exact GPS coordinates:

// This was my naive first approach
public class Memory {
    private double latitude;
    private double longitude;
    private String content;
    private LocalDateTime timestamp;
}
Enter fullscreen mode Exit fullscreen mode

The problem? When you're standing in the "same place" according to GPS, you might be 30 meters off. So I had to get creative:

// What actually worked - area-based grouping
public class Memory {
    private String areaId; // Generated from GPS precision grid
    private double centerLat;
    private double centerLng;
    private double radius; // In meters (typically 50m)
    private List<String> contentIds;
    private LocalDateTime lastUpdated;
}

public class MemoryService {
    public String getAreaId(double lat, double lng) {
        // Create a 50m grid based on GPS precision
        double gridSize = 0.00045; // Roughly 50m at equator
        int gridX = (int)(lat / gridSize);
        int gridY = (int)(lng / gridSize);
        return gridX + "_" + gridY;
    }
}
Enter fullscreen mode Exit fullscreen mode

The Database Schema That Survived Real-World Use

My first attempt was a disaster. I tried to store every memory with full multimedia content in the database. Big mistake.

-- This would have killed my database
CREATE TABLE memories (
    id VARCHAR(255) PRIMARY KEY,
    user_id VARCHAR(255),
    latitude DECIMAL(10, 8),
    longitude DECIMAL(11, 8),
    content LONGBLOB, -- Disaster! Storing images in SQL!
    metadata JSON,
    created_at TIMESTAMP
);
Enter fullscreen mode Exit fullscreen mode

What actually worked was a hybrid approach:

-- The final working schema
CREATE TABLE memory_areas (
    area_id VARCHAR(255) PRIMARY KEY,
    center_lat DECIMAL(10, 8),
    center_lng DECIMAL(11, 8),
    radius DECIMAL(10, 2),
    content_count INTEGER DEFAULT 0,
    last_updated TIMESTAMP,
    INDEX idx_center (center_lat, center_lng)
);

CREATE TABLE memory_content (
    content_id VARCHAR(255) PRIMARY KEY,
    area_id VARCHAR(255),
    user_id VARCHAR(255),
    content_type ENUM('image', 'video', 'text', 'audio'),
    storage_path VARCHAR(255),
    metadata JSON,
    created_at TIMESTAMP,
    FOREIGN KEY (area_id) REFERENCES memory_areas(area_id),
    INDEX idx_area_content (area_id, created_at)
);

CREATE TABLE memory_interactions (
    id VARCHAR(255) PRIMARY KEY,
    area_id VARCHAR(255),
    user_id VARCHAR(255),
    interaction_type ENUM('view', 'add', 'edit', 'delete'),
    device_info JSON,
    interaction_at TIMESTAMP,
    FOREIGN KEY (area_id) REFERENCES memory_areas(area_id)
);
Enter fullscreen mode Exit fullscreen mode

The REST API That Didn't Make Users Rage

Building the API was where I learned the most. My first version tried to do everything at once and ended up being incredibly slow.

// Version 1: The "Everything at Once" Disaster
@RestController
public class MemoryController {
    @GetMapping("/memories/nearby")
    public List<Memory> getMemoriesNearby(
        @RequestParam double lat,
        @RequestParam double lng,
        @RequestParam double radius) {

        // This query would take 47 seconds!
        return memoryRepository.findWithinRadius(lat, lng, radius);
    }
}
Enter fullscreen mode Exit fullscreen mode

The performance issues came from:

  1. Complex spatial calculations
  2. Loading full multimedia content
  3. No pagination
  4. Inefficient database indexes

Version 2 was much smarter:

@RestController
public class MemoryController {

    @GetMapping("/api/areas/nearby")
    public ResponseEntity<AreaResponse> getAreasNearby(
        @RequestParam double lat,
        @RequestParam double lng,
        @RequestParam(defaultValue = "0.01") double radius,
        @RequestParam(defaultValue = "0") int page,
        @RequestParam(defaultValue = "20") int size) {

        // Use pre-calculated areas instead of point-to-point queries
        List<MemoryArea> areas = areaService.findAreasWithinRadius(lat, lng, radius, page, size);

        AreaResponse response = new AreaResponse();
        response.setAreas(areas);
        response.setTotal(areaService.countTotalAreas(lat, lng, radius));

        return ResponseEntity.ok(response);
    }

    @GetMapping("/api/content/{contentId}")
    public ResponseEntity<ContentResponse> getContent(@PathVariable String contentId) {
        ContentResponse content = contentService.getContent(contentId);
        if (content == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(content);
    }
}
Enter fullscreen mode Exit fullscreen mode

The AR Frontend That Didn't Crash Devices

The AR component was my biggest nightmare. WebXR is powerful but incredibly finicky.

// What I thought would work
class SpatialMemoryAR {
    constructor() {
        this.session = new XRSession('immersive-ar');
        this.setupScene();
    }

    setupScene() {
        // Create scene, add camera, lights
        this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        this.scene = new THREE.Scene();

        // Load memories and add them as 3D objects
        this.loadMemories();
    }

    loadMemories() {
        // Load all memories and render them as floating 3D objects
        // This would crash most devices
    }
}
Enter fullscreen mode Exit fullscreen mode

The reality check came when I tested on different devices:

  • High-end phones: Worked okay but drained battery in 30 minutes
  • Mid-range phones: Crashed after 2 minutes
  • Budget phones: Wouldn't even load the AR session

My solution was to create a fallback system:

class SpatialMemoryApp {
    constructor() {
        this.detectDeviceCapabilities();
        this.initializeAppropriateMode();
    }

    detectDeviceCapabilities() {
        this.isHighEnd = this.checkDevicePerformance();
        this.hasWebXR = 'xr' in navigator;
        this.batteryLevel = navigator.getBattery ? navigator.getBattery() : null;
    }

    initializeAppropriateMode() {
        if (this.isHighEnd && this.hasWebXR) {
            this.initializeARMode();
        } else {
            this.initializeMapMode();
        }
    }

    initializeARMode() {
        // Full AR experience with performance monitoring
        this.arSession = new XRSession('immersive-ar');
        this.setupPerformanceMonitoring();
    }

    initializeMapMode() {
        // Google Maps-style fallback
        this.map = new google.maps.Map(document.getElementById('map'), {
            center: {lat: 0, lng: 0},
            zoom: 15
        });
        this.loadMemoriesOnMap();
    }

    setupPerformanceMonitoring() {
        // Monitor battery and performance
        if (this.batteryLevel) {
            this.batteryLevel.addEventListener('levelchange', () => {
                if (this.batteryLevel.level < 0.2) {
                    this.switchToPowerSavingMode();
                }
            });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The Brutal Truths I Learned the Hard Way

Truth #1: GPS Is Not Your Friend

I spent weeks trying to get perfect GPS accuracy. The reality?

  • Ideal conditions: 3-5 meters accuracy
  • Cities with tall buildings: 20-30 meters accuracy
  • Moving devices: Add another 10-20 meters of error

My solution? I stopped trying to pin memories to exact locations and instead created "areas" where memories could exist. This way, if users were in the right general area, they'd see the memories.

Truth #2: AR Rendering Is a Nightmare

WebXR is powerful but:

  • Device compatibility is terrible
  • Battery drain is insane (30-minute sessions are normal)
  • Performance varies wildly between devices
  • User expectations are unrealistic (they expect Hollywood-quality AR)

I learned to focus on the core experience and let the technology catch up.

Truth #3: Storage and Bandwidth Will Kill You

Storing multimedia content in databases is a terrible idea. I learned:

  • Use object storage (S3, etc.) for media
  • Implement proper caching strategies
  • Use CDNs for serving content
  • Compress everything intelligently

Truth #4: User Testing Reveals Everything That's Wrong

My friends and family told me my app was "awesome." Real users told me it was confusing, slow, and drained their battery.

The key lesson: Real user testing reveals problems your friends won't tell you about.

The Surprising Benefits

Despite all the failures, I gained some incredible skills:

  1. Advanced mobile development - I now understand mobile performance like never before
  2. Spatial database design - I know how to work with geospatial data effectively
  3. AR/VR development - I understand the limitations and possibilities of WebXR
  4. Performance optimization - I can make mobile apps actually fast
  5. User experience design - I know what users actually want vs. what they say they want

The Unexpected Business Insight

Here's something I never expected: Failure can be valuable. Despite the -100% ROI, I've gained:

  • Expertise in a niche technical area
  • Real-world experience that textbooks can't teach
  • A portfolio of projects that demonstrate practical skills
  • Understanding of what doesn't work (which is often more valuable than knowing what does)

What I Would Do Differently

If I could start over:

  1. Build simpler first - Start with a map view instead of jumping to AR
  2. User testing first - Test with real users before building complex features
  3. Performance budget - Set strict performance limits from day one
  4. Battery optimization - Design for battery life from the start
  5. Incremental approach - Build, test, learn, repeat instead of building everything at once

The Final Architecture That Works

After all the iterations, here's what I ended up with:

// Final working architecture
@Service
public class SpatialMemoryService {

    @Autowired
    private MemoryAreaRepository areaRepository;

    @Autowired
    private MemoryContentRepository contentRepository;

    @Autowired
    private S3Service s3Service;

    public AreaResponse getMemoriesInArea(double lat, double lng, double radius, int page, int size) {
        // Get areas within radius
        List<MemoryArea> areas = areaRepository.findWithinRadius(lat, lng, radius, page, size);

        // Get metadata for content, not actual content
        List<ContentMetadata> contentMetadata = contentRepository.getMetadataByAreas(
            areas.stream().map(MemoryArea::getAreaId).collect(Collectors.toList())
        );

        return new AreaResponse(areas, contentMetadata, page, size);
    }

    @Transactional
    public ContentResponse addContent(MemoryContentRequest request) {
        // Generate area ID from GPS coordinates
        String areaId = generateAreaId(request.getLatitude(), request.getLongitude());

        // Store media in S3, not database
        String storagePath = s3Service.uploadMedia(request.getMedia(), request.getContentType());

        // Save metadata to database
        MemoryContent content = new MemoryContent();
        content.setAreaId(areaId);
        content.setUserId(request.getUserId());
        content.setContentType(request.getContentType());
        content.setStoragePath(storagePath);
        content.setMetadata(request.getMetadata());

        return new ContentResponse(contentRepository.save(content));
    }
}
Enter fullscreen mode Exit fullscreen mode

The Final Question for You

So here's my question to you: Have you ever built something that was technically amazing but completely failed in the real world?

I'd love to hear about your experiences - the good, the bad, and the ugly. What lessons did you learn that you wish you'd known from the start?

Drop your stories in the comments below, and let's learn from each other's failures (because, let's be honest, we all have them!).


P.S. If you're thinking about building an AR app, I strongly recommend starting with a simpler version first. My full source code is available on GitHub if you want to see all the gory details.

Top comments (0)