Introduction to Indoor Mapping
Indoor mapping enables navigation, location services, and spatial analysis within buildings. Java provides robust capabilities for processing indoor spatial data, managing floor plans, and implementing indoor navigation algorithms.
Core Data Models
Indoor Spatial Data Structures
public class IndoorMap {
private String mapId;
private String buildingName;
private List<Floor> floors;
private List<POI> pointsOfInterest;
private CoordinateReferenceSystem crs;
private BoundingBox bounds;
// Constructors, getters, setters
}
public class Floor {
private String floorId;
private int level;
private String name;
private double altitude;
private Geometry footprint;
private List<Space> spaces;
private List<Wall> walls;
private List<Door> doors;
private List<Facility> facilities;
private ImageMetadata floorPlan;
// Constructors, getters, setters
}
public class Space {
private String spaceId;
private SpaceType type; // ROOM, CORRIDOR, STAIRCASE, ELEVATOR, etc.
private Geometry geometry;
private Map<String, Object> properties;
private List<Connection> connections;
public enum SpaceType {
ROOM, CORRIDOR, HALLWAY, STAIRCASE, ELEVATOR,
ESCALATOR, RESTROOM, OFFICE, MEETING_ROOM, LOBBY
}
}
public class Connection {
private String connectionId;
private String fromSpaceId;
private String toSpaceId;
private ConnectionType type; // DOOR, OPENING, VERTICAL
private Geometry connectionPath;
public enum ConnectionType {
DOOR, OPENING, STAIRS, ELEVATOR, ESCALATOR
}
}
Indoor Navigation System
Graph-Based Navigation
@Component
public class IndoorNavigationEngine {
private Map<String, IndoorGraph> buildingGraphs = new ConcurrentHashMap<>();
public NavigationRoute calculateRoute(RouteRequest request) {
IndoorGraph graph = getOrBuildGraph(request.getBuildingId());
Node startNode = findNearestNode(graph, request.getStartPoint(), request.getFloorLevel());
Node endNode = findNearestNode(graph, request.getEndPoint(), request.getEndFloorLevel());
return findShortestPath(graph, startNode, endNode, request.getPreferences());
}
private IndoorGraph buildGraph(IndoorMap indoorMap) {
IndoorGraph graph = new IndoorGraph();
// Add nodes for all spaces
for (Floor floor : indoorMap.getFloors()) {
for (Space space : floor.getSpaces()) {
Node node = new Node(space.getSpaceId(), space.getGeometry().getCentroid(), floor.getLevel());
graph.addNode(node);
}
}
// Add edges for connections
for (Floor floor : indoorMap.getFloors()) {
for (Space space : floor.getSpaces()) {
for (Connection connection : space.getConnections()) {
Node fromNode = graph.getNode(connection.getFromSpaceId());
Node toNode = graph.getNode(connection.getToSpaceId());
double weight = calculateConnectionWeight(connection, fromNode, toNode);
graph.addEdge(fromNode, toNode, weight, connection.getType());
}
}
}
// Connect vertical transitions (stairs, elevators)
connectVerticalTransitions(graph, indoorMap);
return graph;
}
private NavigationRoute findShortestPath(IndoorGraph graph, Node start, Node end,
RoutePreferences preferences) {
// Implement A* algorithm for indoor navigation
Map<Node, Double> gScore = new HashMap<>();
Map<Node, Double> fScore = new HashMap<>();
Map<Node, Node> cameFrom = new HashMap<>();
PriorityQueue<Node> openSet = new PriorityQueue<>(
Comparator.comparingDouble(n -> fScore.getOrDefault(n, Double.MAX_VALUE)));
gScore.put(start, 0.0);
fScore.put(start, heuristicCost(start, end));
openSet.add(start);
while (!openSet.isEmpty()) {
Node current = openSet.poll();
if (current.equals(end)) {
return reconstructRoute(cameFrom, current, preferences);
}
for (Edge edge : graph.getEdgesFrom(current)) {
Node neighbor = edge.getToNode();
double tentativeGScore = gScore.get(current) + edge.getWeight();
if (tentativeGScore < gScore.getOrDefault(neighbor, Double.MAX_VALUE)) {
cameFrom.put(neighbor, current);
gScore.put(neighbor, tentativeGScore);
fScore.put(neighbor, tentativeGScore + heuristicCost(neighbor, end));
if (!openSet.contains(neighbor)) {
openSet.add(neighbor);
}
}
}
}
throw new NoRouteFoundException("No path found from " + start + " to " + end);
}
}
public class IndoorGraph {
private Map<String, Node> nodes = new HashMap<>();
private Map<String, List<Edge>> edges = new HashMap<>();
public void addNode(Node node) {
nodes.put(node.getId(), node);
edges.put(node.getId(), new ArrayList<>());
}
public void addEdge(Node from, Node to, double weight, Connection.ConnectionType type) {
edges.get(from.getId()).add(new Edge(from, to, weight, type));
// Add bidirectional edge for most connection types
if (type != Connection.ConnectionType.DOOR) { // Doors might be one-way
edges.get(to.getId()).add(new Edge(to, from, weight, type));
}
}
public List<Edge> getEdgesFrom(Node node) {
return edges.getOrDefault(node.getId(), Collections.emptyList());
}
}
Indoor Positioning System (IPS)
Multi-technology Positioning
@Service
public class IndoorPositioningService {
@Autowired
private BluetoothBeaconService bluetoothService;
@Autowired
private WiFiPositioningService wifiService;
@Autowired
private MagneticFieldService magneticService;
public Position estimatePosition(PositioningRequest request) {
List<PositionEstimate> estimates = new ArrayList<>();
// Fusion of multiple positioning technologies
if (request.hasBluetoothData()) {
estimates.add(bluetoothService.estimatePosition(request.getBluetoothReadings()));
}
if (request.hasWiFiData()) {
estimates.add(wifiService.estimatePosition(request.getWiFiScanResults()));
}
if (request.hasMagneticData()) {
estimates.add(magneticService.estimatePosition(request.getMagneticReadings()));
}
if (request.hasInertialData()) {
estimates.add(applyPedestrianDeadReckoning(
request.getInertialData(), request.getLastKnownPosition()));
}
return fusePositionEstimates(estimates, request.getBuildingId());
}
private Position fusePositionEstimates(List<PositionEstimate> estimates, String buildingId) {
// Implement sensor fusion algorithm (Kalman filter, particle filter)
if (estimates.isEmpty()) {
throw new PositioningException("No positioning data available");
}
if (estimates.size() == 1) {
return estimates.get(0).getPosition();
}
// Weighted average based on confidence and technology
Map<Position, Double> weightedPositions = new HashMap<>();
for (PositionEstimate estimate : estimates) {
double weight = calculateEstimateWeight(estimate);
weightedPositions.merge(estimate.getPosition(), weight, Double::sum);
}
// Find position with highest cumulative weight
return weightedPositions.entrySet().stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey)
.orElseThrow(() -> new PositioningException("Position fusion failed"));
}
}
@Component
public class BluetoothBeaconService {
private Map<String, BeaconReference> beaconDatabase = new ConcurrentHashMap<>();
public PositionEstimate estimatePosition(List<BeaconReading> readings) {
if (readings.size() < 3) {
throw new InsufficientBeaconsException("At least 3 beacons required for triangulation");
}
// Convert RSSI to distance
List<BeaconDistance> distances = readings.stream()
.map(this::rssiToDistance)
.collect(Collectors.toList());
// Trilateration algorithm
Position position = performTrilateration(distances);
double confidence = calculatePositionConfidence(distances, position);
return new PositionEstimate(position, confidence, PositioningTechnology.BLUETOOTH);
}
private Position performTrilateration(List<BeaconDistance> distances) {
// Implement least-squares trilateration
// This is a simplified version - real implementation would use more sophisticated math
double x = 0, y = 0, totalWeight = 0;
for (BeaconDistance distance : distances) {
BeaconReference beacon = beaconDatabase.get(distance.getBeaconId());
double weight = 1.0 / (distance.getDistance() + 0.1); // Weight by inverse distance
x += beacon.getPosition().getX() * weight;
y += beacon.getPosition().getY() * weight;
totalWeight += weight;
}
return new Position(x / totalWeight, y / totalWeight, 0); // Z=0 for single floor
}
}
Floor Plan Processing
CAD/DWG to Indoor Map Conversion
@Service
public class FloorPlanProcessor {
@Autowired
private GeometryFactory geometryFactory;
public IndoorMap processDXFFile(File dxfFile, BuildingMetadata metadata) {
try {
DXFDocument dxfDocument = DXFDocument.load(dxfFile);
List<Floor> floors = new ArrayList<>();
// Process layers as different floor levels
for (DXFLayer layer : dxfDocument.getLayers()) {
if (isFloorLayer(layer)) {
Floor floor = processFloorLayer(layer, metadata);
floors.add(floor);
}
}
return new IndoorMap(metadata.getBuildingId(), metadata.getBuildingName(), floors);
} catch (Exception e) {
throw new FloorPlanProcessingException("Failed to process DXF file", e);
}
}
private Floor processFloorLayer(DXFLayer layer, BuildingMetadata metadata) {
List<Space> spaces = new ArrayList<>();
List<Wall> walls = new ArrayList<>();
List<Door> doors = new ArrayList<>();
for (DXFEntity entity : layer.getEntities()) {
switch (entity.getType()) {
case "LWPOLYLINE":
processPolyline((DXFLWPolyline) entity, spaces, walls);
break;
case "LINE":
processLine((DXFLine) entity, walls);
break;
case "INSERT":
processBlockReference((DXFInsert) entity, doors, spaces);
break;
case "TEXT":
processText((DXFText) entity, spaces);
break;
}
}
// Infer connections between spaces
List<Connection> connections = inferConnections(spaces, doors);
int floorLevel = extractFloorLevel(layer.getName());
String floorName = metadata.getFloorName(floorLevel);
return new Floor(
UUID.randomUUID().toString(),
floorLevel,
floorName,
calculateFloorAltitude(floorLevel),
calculateFootprint(spaces),
spaces,
walls,
doors,
Collections.emptyList(), // facilities would be processed separately
null // floor plan image
);
}
private void processPolyline(DXFLWPolyline polyline, List<Space> spaces, List<Wall> walls) {
Geometry geometry = geometryFactory.createPolygon(
polyline.getVertices().stream()
.map(v -> new Coordinate(v.x, v.y))
.toArray(Coordinate[]::new)
);
if (isRoomPolyline(polyline)) {
Space room = new Space(
UUID.randomUUID().toString(),
Space.SpaceType.ROOM,
geometry,
extractRoomProperties(polyline)
);
spaces.add(room);
} else {
Wall wall = new Wall(geometry, extractWallProperties(polyline));
walls.add(wall);
}
}
private List<Connection> inferConnections(List<Space> spaces, List<Door> doors) {
List<Connection> connections = new ArrayList<>();
for (Door door : doors) {
// Find spaces that this door connects
List<Space> connectedSpaces = findSpacesTouchingDoor(spaces, door);
if (connectedSpaces.size() >= 2) {
for (int i = 0; i < connectedSpaces.size(); i++) {
for (int j = i + 1; j < connectedSpaces.size(); j++) {
Connection connection = new Connection(
UUID.randomUUID().toString(),
connectedSpaces.get(i).getSpaceId(),
connectedSpaces.get(j).getSpaceId(),
Connection.ConnectionType.DOOR,
door.getGeometry()
);
connections.add(connection);
}
}
}
}
return connections;
}
}
REST API for Indoor Mapping
@RestController
@RequestMapping("/api/indoor")
public class IndoorMappingController {
@Autowired
private IndoorNavigationEngine navigationEngine;
@Autowired
private IndoorPositioningService positioningService;
@Autowired
private IndoorMapService mapService;
@GetMapping("/maps/{buildingId}")
public ResponseEntity<IndoorMap> getBuildingMap(@PathVariable String buildingId) {
IndoorMap map = mapService.getIndoorMap(buildingId);
return ResponseEntity.ok(map);
}
@PostMapping("/navigation/route")
public ResponseEntity<NavigationRoute> calculateRoute(@RequestBody RouteRequest request) {
try {
NavigationRoute route = navigationEngine.calculateRoute(request);
return ResponseEntity.ok(route);
} catch (NoRouteFoundException e) {
return ResponseEntity.notFound().build();
}
}
@PostMapping("/positioning/estimate")
public ResponseEntity<Position> estimatePosition(@RequestBody PositioningRequest request) {
try {
Position position = positioningService.estimatePosition(request);
return ResponseEntity.ok(position);
} catch (PositioningException e) {
return ResponseEntity.badRequest().build();
}
}
@GetMapping("/maps/{buildingId}/floor/{level}/features")
public ResponseEntity<List<IndoorFeature>> getFloorFeatures(
@PathVariable String buildingId,
@PathVariable int level,
@RequestParam(required = false) String featureType) {
List<IndoorFeature> features = mapService.getFloorFeatures(buildingId, level, featureType);
return ResponseEntity.ok(features);
}
@GetMapping("/maps/{buildingId}/search")
public ResponseEntity<List<SearchResult>> searchIndoor(
@PathVariable String buildingId,
@RequestParam String query,
@RequestParam(defaultValue = "10") int limit) {
List<SearchResult> results = mapService.search(buildingId, query, limit);
return ResponseEntity.ok(results);
}
}
Real-time Location Tracking
@Service
public class RealTimeTrackingService {
private final Map<String, UserSession> activeSessions = new ConcurrentHashMap<>();
private final List<LocationListener> listeners = new CopyOnWriteArrayList<>();
@Autowired
private SimpMessagingTemplate messagingTemplate;
public void updateUserPosition(String userId, String buildingId, Position position) {
UserSession session = activeSessions.computeIfAbsent(userId,
id -> new UserSession(id, buildingId));
session.updatePosition(position);
// Notify listeners
LocationUpdate update = new LocationUpdate(userId, position, System.currentTimeMillis());
for (LocationListener listener : listeners) {
listener.onLocationUpdate(update);
}
// Broadcast via WebSocket
messagingTemplate.convertAndSend(
"/topic/location/" + buildingId + "/" + userId,
update
);
}
public List<UserPosition> getActiveUsersInArea(String buildingId, BoundingBox area) {
return activeSessions.values().stream()
.filter(session -> buildingId.equals(session.getBuildingId()))
.filter(session -> area.contains(session.getCurrentPosition()))
.map(session -> new UserPosition(
session.getUserId(),
session.getCurrentPosition(),
session.getLastUpdateTime()))
.collect(Collectors.toList());
}
public void addLocationListener(LocationListener listener) {
listeners.add(listener);
}
}
@Controller
public class LocationWebSocketController {
@Autowired
private RealTimeTrackingService trackingService;
@MessageMapping("/location/update")
@SendTo("/topic/location/broadcast")
public LocationUpdate handleLocationUpdate(LocationMessage message) {
trackingService.updateUserPosition(
message.getUserId(),
message.getBuildingId(),
message.getPosition()
);
return new LocationUpdate(
message.getUserId(),
message.getPosition(),
System.currentTimeMillis()
);
}
}
Data Persistence
JPA Entities for Indoor Mapping
@Entity
@Table(name = "indoor_maps")
public class IndoorMapEntity {
@Id
private String mapId;
private String buildingName;
private String description;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "map_id")
private List<FloorEntity> floors;
@ElementCollection
@CollectionTable(name = "map_metadata", joinColumns = @JoinColumn(name = "map_id"))
@MapKeyColumn(name = "metadata_key")
@Column(name = "metadata_value")
private Map<String, String> metadata;
// Spatial column for bounding box
@Column(columnDefinition = "geometry")
private Geometry bounds;
// Constructors, getters, setters
}
@Entity
@Table(name = "floors")
public class FloorEntity {
@Id
private String floorId;
private String mapId;
private int level;
private String name;
private double altitude;
@Column(columnDefinition = "geometry")
private Geometry footprint;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "floor_id")
private List<SpaceEntity> spaces;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private ImageMetadata floorPlanMetadata;
// Constructors, getters, setters
}
@Entity
@Table(name = "spaces")
public class SpaceEntity {
@Id
private String spaceId;
private String floorId;
@Enumerated(EnumType.STRING)
private SpaceType type;
@Column(columnDefinition = "geometry")
private Geometry geometry;
@Type(type = "jsonb")
@Column(columnDefinition = "jsonb")
private Map<String, Object> properties;
// Constructors, getters, setters
}
Configuration and Dependencies
Maven Dependencies
<dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- Spatial Database Support --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-spatial</artifactId> </dependency> <!-- Geometry Processing --> <dependency> <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> <version>1.18.2</version> </dependency> <!-- DXF Processing --> <dependency> <groupId>org.kabeja</groupId> <artifactId>kabeja</artifactId> <version>0.4</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency> <!-- PostgreSQL with PostGIS --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency> </dependencies>
Conclusion
This comprehensive indoor mapping system in Java provides:
- Spatial Data Management - Structured representation of buildings, floors, and spaces
- Indoor Navigation - Graph-based pathfinding with multi-floor support
- Positioning Services - Multi-technology indoor positioning with sensor fusion
- Floor Plan Processing - Automated conversion from CAD formats to indoor maps
- Real-time Tracking - WebSocket-based location updates and user tracking
- RESTful APIs - Comprehensive API for indoor mapping services
The system supports complex indoor environments with multiple floors, various space types, and different positioning technologies, making it suitable for applications in airports, hospitals, shopping malls, and large office buildings.
Java Observability, Logging Intelligence & AI-Driven Monitoring (APM, Tracing, Logs & Anomaly Detection)
https://macronepal.com/blog/beyond-metrics-observing-serverless-and-traditional-java-applications-with-thundra-apm/
Explains using Thundra APM to observe both serverless and traditional Java applications by combining tracing, metrics, and logs into a unified observability platform for faster debugging and performance insights.
https://macronepal.com/blog/dynatrace-oneagent-in-java-2/
Explains Dynatrace OneAgent for Java, which automatically instruments JVM applications to capture metrics, traces, and logs, enabling full-stack monitoring and root-cause analysis with minimal configuration.
https://macronepal.com/blog/lightstep-java-sdk-distributed-tracing-and-observability-implementation/
Explains Lightstep Java SDK for distributed tracing, helping developers track requests across microservices and identify latency issues using OpenTelemetry-based observability.
https://macronepal.com/blog/honeycomb-io-beeline-for-java-complete-guide-2/
Explains Honeycomb Beeline for Java, which provides high-cardinality observability and deep query capabilities to understand complex system behavior and debug distributed systems efficiently.
https://macronepal.com/blog/lumigo-for-serverless-in-java-complete-distributed-tracing-guide-2/
Explains Lumigo for Java serverless applications, offering automatic distributed tracing, log correlation, and error tracking to simplify debugging in cloud-native environments. (Lumigo Docs)
https://macronepal.com/blog/from-noise-to-signals-implementing-log-anomaly-detection-in-java-applications/
Explains how to detect anomalies in Java logs using behavioral patterns and machine learning techniques to separate meaningful incidents from noisy log data and improve incident response.
https://macronepal.com/blog/ai-powered-log-analysis-in-java-from-reactive-debugging-to-proactive-insights/
Explains AI-driven log analysis for Java applications, shifting from manual debugging to predictive insights that identify issues early and improve system reliability using intelligent log processing.
https://macronepal.com/blog/titliel-java-logging-best-practices/
Explains best practices for Java logging, focusing on structured logs, proper log levels, performance optimization, and ensuring logs are useful for debugging and observability systems.
https://macronepal.com/blog/seeking-a-loguru-for-java-the-quest-for-elegant-and-simple-logging/
Explains the search for simpler, more elegant logging frameworks in Java, comparing modern logging approaches that aim to reduce complexity while improving readability and developer experience.