Skip to main content

Overview

OpenPlans supports headless generation of floor plans from JSON data structures. This enables:
  • Programmatic floor plan creation from databases
  • Serialization and deserialization of designs
  • Integration with external data sources
  • Batch processing of building data

JSON Floor Plan Structure

A complete floor plan is defined using a hierarchical JSON structure:
{
  "building_id": "Building_123",
  "building_name": "Building Name",
  "floors": [],
  "rooms": [],
  "walls": [],
  "windows": [],
  "doors": []
}

Creating Elements from JSON

All OpenPlans elements support creation from configuration objects.

Primitives from JSON

// Line from JSON
const lineConfig = {
  ogid: "line-001",
  startPoint: [0, 0, 0],
  endPoint: [10, 0, 0],
  color: 0x000000
};
const line = openPlans.line(lineConfig);

// Rectangle from JSON
const rectConfig = {
  ogid: "rect-001",
  center: [5, 0, 5],
  width: 10,
  breadth: 8,
  color: 0x0000FF
};
const rectangle = openPlans.rectangle(rectConfig);

// Polyline from JSON
const polylineConfig = {
  ogid: "poly-001",
  points: [
    [0, 0, 0],
    [5, 0, 0],
    [5, 0, 3],
    [0, 0, 3],
    [0, 0, 0]
  ],
  color: 0x000000
};
const polyline = openPlans.polyline(polylineConfig);

Architectural Elements from JSON

// Door from JSON
const doorConfig = {
  type: "door",
  labelName: "Main Entrance",
  dimensions: {
    start: { x: 0, y: 0, z: 0 },
    end: { x: 0, y: 0, z: 0 },
    length: 2
  },
  doorPosition: [3, 0, 3],
  doorType: "WOOD",
  doorHeight: 2.1,
  doorThickness: 0.2,
  frameThickness: 0.2,
  doorColor: 9127187,
  frameColor: 0,
  doorRotation: 1.5,
  doorQuadrant: 2,
  coordinates: [],
  id: "door-001"
};
const door = openPlans.baseDoor(doorConfig);

// Window from JSON  
const windowConfig = {
  labelName: "Bedroom Window",
  windowPosition: [2, 0, 5],
  windowLength: 1.5,
  windowHeight: 1.2,
  windowThickness: 0.1,
  windowColor: 11393254,
  frameColor: 0
};
const window = openPlans.baseSingleWindow(windowConfig);

3D Shapes from JSON

import { Vector3 } from 'openplans';

// Cuboid from JSON
const cuboidConfig = {
  ogid: '86302abc-d71e-47be-affc-dc05de1db8fe',
  width: 20,
  height: 10,
  depth: 1,
  color: 0x000000,
  center: new Vector3(20, 0, 0)
};
const cuboid = openPlans.cuboid(cuboidConfig);

// Cylinder from JSON
const cylinderConfig = {
  ogid: 'cylinder-001',
  center: new Vector3(5, 1, 5),
  radius: 0.3,
  height: 2,
  segments: 32,
  color: 0xCCCCCC
};
const cylinder = openPlans.cylinder(cylinderConfig);

Complete Floor Plan JSON Example

Here’s a complete JSON structure for a multi-room floor plan:
{
  "building_id": "Building_123",
  "building_name": "Residential Building",
  "floors": [
    {
      "OG_ID": "Floor_1",
      "OG_TYPE": "OG_FLOOR",
      "OG_DATA": ["Room_1", "Room_2"]
    }
  ],
  "rooms": [
    {
      "OG_ID": "Room_1",
      "OG_TYPE": "OG_ROOM",
      "type": "Bedroom",
      "OG_DATA": [
        {"OG_ID": "Wall_1", "OG_TYPE": "OG_WALL"},
        {"OG_ID": "Window_1", "OG_TYPE": "OG_WINDOW"},
        {"OG_ID": "Door_1", "OG_TYPE": "OG_DOOR"}
      ],
      "coordinates": [[0, 0, 0], [4, 0, 0], [4, 0, 3.5], [0, 0, 3.5]],
      "connections": ["Room_2"]
    },
    {
      "OG_ID": "Room_2",
      "OG_TYPE": "OG_ROOM",
      "type": "Living Room",
      "OG_DATA": [
        {"OG_ID": "Wall_2", "OG_TYPE": "OG_WALL"},
        {"OG_ID": "Window_2", "OG_TYPE": "OG_WINDOW"}
      ],
      "coordinates": [[4, 0, 0], [10, 0, 0], [10, 0, 4], [4, 0, 4]],
      "connections": ["Room_1"]
    }
  ],
  "walls": [
    {
      "OG_ID": "Wall_1",
      "OG_TYPE": "OG_WALL",
      "type": "internal",
      "thickness": 0.2,
      "start": [4, 0, 0],
      "end": [4, 0, 3.5]
    },
    {
      "OG_ID": "Wall_2",
      "OG_TYPE": "OG_WALL",
      "type": "external",
      "thickness": 0.3,
      "start": [0, 0, 0],
      "end": [0, 0, 3.5]
    }
  ],
  "windows": [
    {
      "OG_ID": "Window_1",
      "OG_TYPE": "OG_WINDOW",
      "type": "internal",
      "thickness": 0.2,
      "start": [2, 0, 3.5],
      "end": [2, 0, 4.5]
    }
  ],
  "doors": [
    {
      "OG_ID": "Door_1",
      "OG_TYPE": "OG_DOOR",
      "type": "internal",
      "thickness": 0.25,
      "hingeThickness": 0.125,
      "start": [0, 0, 1.0],
      "end": [0, 0, 1.8],
      "position": [0, 0, 1.0]
    }
  ]
}

Loading JSON Floor Plans

From External File

import { OpenPlans } from 'openplans';

async function loadFloorPlanFromJSON() {
  const openPlans = new OpenPlans(container);
  await openPlans.setupOpenGeometry();
  
  // Fetch JSON data
  const response = await fetch('./floor-plan.json');
  const floorPlanData = await response.json();
  
  // Process the JSON and create elements
  generateFloorPlan(openPlans, floorPlanData);
}

function generateFloorPlan(openPlans, data) {
  // Create walls
  data.walls?.forEach(wall => {
    openPlans.line({
      ogid: wall.OG_ID,
      startPoint: wall.start,
      endPoint: wall.end,
      color: 0x000000
    });
  });
  
  // Create doors
  data.doors?.forEach(door => {
    openPlans.baseDoor({
      labelName: door.OG_ID,
      doorPosition: door.position,
      doorLength: calculateLength(door.start, door.end),
      doorHeight: 2.1,
      doorThickness: door.thickness
    });
  });
  
  // Create windows
  data.windows?.forEach(window => {
    openPlans.baseSingleWindow({
      labelName: window.OG_ID,
      windowPosition: window.start,
      windowLength: calculateLength(window.start, window.end),
      windowHeight: 1.2
    });
  });
}

function calculateLength(start, end) {
  const dx = end[0] - start[0];
  const dy = end[1] - start[1];
  const dz = end[2] - start[2];
  return Math.sqrt(dx*dx + dy*dy + dz*dz);
}

From URL

// Load from remote URL
const jsonURL = "https://example.com/floor-plans/building-123.json";
const response = await fetch(jsonURL);
const floorPlanData = await response.json();

generateFloorPlan(openPlans, floorPlanData);

Exporting to JSON

Exporting Element Configurations

// Get configuration from any element
const doorConfig = door.getOPConfig();
const lineConfig = line.getOPConfig();
const rectConfig = rectangle.getOPConfig();

// Serialize to JSON
const doorJSON = JSON.stringify(doorConfig, null, 2);
console.log(doorJSON);

Exporting Complete Floor Plans

function exportFloorPlan(openPlans) {
  const floorPlan = {
    building_id: "exported-building",
    building_name: "Exported Floor Plan",
    timestamp: new Date().toISOString(),
    elements: []
  };
  
  // Export all lines
  const lines = openPlans.getEntitiesByType('LinePrimitive');
  lines.forEach(line => {
    floorPlan.elements.push({
      type: 'line',
      config: line.getOPConfig()
    });
  });
  
  // Export all rectangles
  const rectangles = openPlans.getEntitiesByType('RectanglePrimitive');
  rectangles.forEach(rect => {
    floorPlan.elements.push({
      type: 'rectangle',
      config: rect.getOPConfig()
    });
  });
  
  // Export all doors
  const doors = openPlans.getEntitiesByType('baseDoor');
  doors.forEach(door => {
    floorPlan.elements.push({
      type: 'door',
      config: door.getOPConfig()
    });
  });
  
  // Export all windows
  const windows = [
    ...openPlans.getEntitiesByType('baseSingleWindow'),
    ...openPlans.getEntitiesByType('baseDoubleWindow')
  ];
  windows.forEach(window => {
    floorPlan.elements.push({
      type: 'window',
      config: window.getOPConfig()
    });
  });
  
  return JSON.stringify(floorPlan, null, 2);
}

// Export and download
const jsonData = exportFloorPlan(openPlans);
const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'floor-plan.json';
link.click();

Format Conversion

OpenPlans includes a converter for the Implenia format:
import { convertToOGFormat } from 'openplans';

// Load external format
const impleniaData = await fetch('./implenia-format.json');
const impleniaJSON = await impleniaData.json();

// Convert to OpenGeometry format
const ogFormat = convertToOGFormat(impleniaJSON);

// Use with OpenPlans
openPlans.convertImpleniaToOGFormat(impleniaJSON);

Batch Processing

Process multiple floor plans from JSON:
async function processBatch(floorPlanURLs) {
  const container = document.getElementById('app');
  const openPlans = new OpenPlans(container);
  await openPlans.setupOpenGeometry();
  
  for (const url of floorPlanURLs) {
    // Load floor plan
    const response = await fetch(url);
    const data = await response.json();
    
    // Generate floor plan
    generateFloorPlan(openPlans, data);
    
    // Export as image or perform analysis
    // ...
    
    // Clear for next floor plan
    clearAllElements(openPlans);
  }
}

function clearAllElements(openPlans) {
  const allElements = [
    ...openPlans.getEntitiesByType('LinePrimitive'),
    ...openPlans.getEntitiesByType('RectanglePrimitive'),
    ...openPlans.getEntitiesByType('baseDoor'),
    ...openPlans.getEntitiesByType('baseSingleWindow')
  ];
  
  allElements.forEach(element => {
    openPlans.disposeElement(element.ogid);
  });
}

Validation and Error Handling

function validateFloorPlanJSON(data) {
  const errors = [];
  
  // Check required fields
  if (!data.building_id) {
    errors.push('Missing building_id');
  }
  
  // Validate rooms
  if (data.rooms) {
    data.rooms.forEach((room, index) => {
      if (!room.OG_ID) {
        errors.push(`Room ${index} missing OG_ID`);
      }
      if (!room.coordinates || room.coordinates.length < 3) {
        errors.push(`Room ${room.OG_ID} has invalid coordinates`);
      }
    });
  }
  
  // Validate walls
  if (data.walls) {
    data.walls.forEach((wall, index) => {
      if (!wall.start || !wall.end) {
        errors.push(`Wall ${wall.OG_ID || index} missing start or end`);
      }
    });
  }
  
  return {
    isValid: errors.length === 0,
    errors
  };
}

// Use validation
const validation = validateFloorPlanJSON(floorPlanData);
if (!validation.isValid) {
  console.error('Invalid floor plan data:', validation.errors);
} else {
  generateFloorPlan(openPlans, floorPlanData);
}

Complete Example

import { OpenPlans } from 'openplans';

async function createFromJSON() {
  const container = document.getElementById('app');
  const openPlans = new OpenPlans(container);
  await openPlans.setupOpenGeometry();
  
  // Sample JSON data
  const floorPlanJSON = {
    building_id: "sample-001",
    building_name: "Sample Apartment",
    rooms: [
      {
        id: "bedroom",
        type: "bedroom",
        outline: [[0, 0, 0], [4, 0, 0], [4, 0, 3.5], [0, 0, 3.5], [0, 0, 0]]
      },
      {
        id: "living",
        type: "living_room",
        outline: [[4, 0, 0], [10, 0, 0], [10, 0, 5], [4, 0, 5], [4, 0, 0]]
      }
    ],
    doors: [
      {
        id: "door-1",
        position: [0, 0, 1],
        length: 2,
        quadrant: 1
      }
    ],
    windows: [
      {
        id: "window-1",
        position: [2, 0, 3.5],
        length: 1.5
      }
    ]
  };
  
  // Generate rooms
  floorPlanJSON.rooms.forEach(room => {
    openPlans.polyline({
      ogid: room.id,
      points: room.outline,
      color: 0x000000
    });
    
    // Add label
    const center = calculateCenter(room.outline);
    const label = openPlans.glyph(
      room.type.replace('_', ' '),
      6,
      0x000000
    );
    label.position.set(center[0], center[1], center[2]);
  });
  
  // Generate doors
  floorPlanJSON.doors.forEach(door => {
    openPlans.baseDoor({
      labelName: door.id,
      doorPosition: door.position,
      doorLength: door.length,
      doorHeight: 2.1,
      doorQuadrant: door.quadrant
    });
  });
  
  // Generate windows
  floorPlanJSON.windows.forEach(window => {
    openPlans.baseSingleWindow({
      labelName: window.id,
      windowPosition: window.position,
      windowLength: window.length,
      windowHeight: 1.2
    });
  });
}

function calculateCenter(points) {
  const sum = points.reduce((acc, p) => [
    acc[0] + p[0],
    acc[1] + p[1],
    acc[2] + p[2]
  ], [0, 0, 0]);
  
  return [
    sum[0] / points.length,
    sum[1] / points.length,
    sum[2] / points.length
  ];
}

createFromJSON();

Next Steps

Last modified on March 7, 2026