Overview
Leaflet Server provides backend services for managing interactive maps, geographic data, and map rendering. This Java implementation includes spatial data storage, tile serving, geocoding, routing, and real-time map features.
1. Dependencies
<dependencies> <!-- Spring Boot --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> <version>3.1.0</version> </dependency> <!-- Spatial Database Support --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-spatial</artifactId> <version>6.2.7.Final</version> </dependency> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <version>42.6.0</version> </dependency> <dependency> <groupId>net.postgis</groupId> <artifactId>postgis-jdbc</artifactId> <version>2.5.1</version> </dependency> <!-- Geospatial Libraries --> <dependency> <groupId>org.locationtech.jts</groupId> <artifactId>jts-core</artifactId> <version>1.19.0</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-main</artifactId> <version>28.2</version> </dependency> <dependency> <groupId>org.geotools</groupId> <artifactId>gt-geojson</artifactId> <version>28.2</version> </dependency> <!-- Image Processing for Tiles --> <dependency> <groupId>com.twelvemonkeys.imageio</groupId> <artifactId>imageio-core</artifactId> <version>3.9.4</version> </dependency> <dependency> <groupId>com.twelvemonkeys.imageio</groupId> <artifactId>imageio-jpeg</artifactId> <version>3.9.4</version> </dependency> <dependency> <groupId>com.twelvemonkeys.imageio</groupId> <artifactId>imageio-png</artifactId> <version>3.9.4</version> </dependency> <!-- Caching --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> <version>3.1.6</version> </dependency> <!-- JSON Processing --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.15.2</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> <!-- Utilities --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.13.0</version> </dependency> <!-- Logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.7</version> </dependency> </dependencies>
2. Core Domain Models
Spatial Data Models
package com.example.leaflet.model;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Map;
@Entity
@Table(name = "map_layers")
public class MapLayer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private LayerType type;
@Column(name = "is_visible")
private Boolean isVisible = true;
@Column(name = "is_base_layer")
private Boolean isBaseLayer = false;
@Column(name = "z_index")
private Integer zIndex = 0;
@Column(name = "opacity")
private Double opacity = 1.0;
@Column(name = "min_zoom")
private Integer minZoom = 0;
@Column(name = "max_zoom")
private Integer maxZoom = 18;
@Column(name = "tile_url_template")
private String tileUrlTemplate;
@Column(name = "data_source")
private String dataSource; // URL, file path, or database query
@Enumerated(EnumType.STRING)
private DataFormat dataFormat;
@ElementCollection
@CollectionTable(name = "layer_style", joinColumns = @JoinColumn(name = "layer_id"))
@MapKeyColumn(name = "style_key")
@Column(name = "style_value")
private Map<String, String> style;
@ElementCollection
@CollectionTable(name = "layer_metadata", joinColumns = @JoinColumn(name = "layer_id"))
@MapKeyColumn(name = "meta_key")
@Column(name = "meta_value")
private Map<String, String> metadata;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public LayerType getType() { return type; }
public void setType(LayerType type) { this.type = type; }
public Boolean getIsVisible() { return isVisible; }
public void setIsVisible(Boolean isVisible) { this.isVisible = isVisible; }
public Boolean getIsBaseLayer() { return isBaseLayer; }
public void setIsBaseLayer(Boolean isBaseLayer) { this.isBaseLayer = isBaseLayer; }
public Integer getZIndex() { return zIndex; }
public void setZIndex(Integer zIndex) { this.zIndex = zIndex; }
public Double getOpacity() { return opacity; }
public void setOpacity(Double opacity) { this.opacity = opacity; }
public Integer getMinZoom() { return minZoom; }
public void setMinZoom(Integer minZoom) { this.minZoom = minZoom; }
public Integer getMaxZoom() { return maxZoom; }
public void setMaxZoom(Integer maxZoom) { this.maxZoom = maxZoom; }
public String getTileUrlTemplate() { return tileUrlTemplate; }
public void setTileUrlTemplate(String tileUrlTemplate) { this.tileUrlTemplate = tileUrlTemplate; }
public String getDataSource() { return dataSource; }
public void setDataSource(String dataSource) { this.dataSource = dataSource; }
public DataFormat getDataFormat() { return dataFormat; }
public void setDataFormat(DataFormat dataFormat) { this.dataFormat = dataFormat; }
public Map<String, String> getStyle() { return style; }
public void setStyle(Map<String, String> style) { this.style = style; }
public Map<String, String> getMetadata() { return metadata; }
public void setMetadata(Map<String, String> metadata) { this.metadata = metadata; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
}
@Entity
@Table(name = "spatial_features")
public class SpatialFeature {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "layer_id", nullable = false)
private MapLayer layer;
@Column(columnDefinition = "geometry(Geometry,4326)")
@JsonIgnore
private Geometry geometry;
@Enumerated(EnumType.STRING)
private FeatureType featureType;
@ElementCollection
@CollectionTable(name = "feature_properties", joinColumns = @JoinColumn(name = "feature_id"))
@MapKeyColumn(name = "property_key")
@Column(name = "property_value")
private Map<String, String> properties;
@ElementCollection
@CollectionTable(name = "feature_style", joinColumns = @JoinColumn(name = "feature_id"))
@MapKeyColumn(name = "style_key")
@Column(name = "style_value")
private Map<String, String> style;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public MapLayer getLayer() { return layer; }
public void setLayer(MapLayer layer) { this.layer = layer; }
public Geometry getGeometry() { return geometry; }
public void setGeometry(Geometry geometry) { this.geometry = geometry; }
public FeatureType getFeatureType() { return featureType; }
public void setFeatureType(FeatureType featureType) { this.featureType = featureType; }
public Map<String, String> getProperties() { return properties; }
public void setProperties(Map<String, String> properties) { this.properties = properties; }
public Map<String, String> getStyle() { return style; }
public void setStyle(Map<String, String> style) { this.style = style; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
// Helper methods for common geometry types
public Point getAsPoint() {
return geometry instanceof Point ? (Point) geometry : null;
}
public Polygon getAsPolygon() {
return geometry instanceof Polygon ? (Polygon) geometry : null;
}
public Double getLatitude() {
Point point = getAsPoint();
return point != null ? point.getY() : null;
}
public Double getLongitude() {
Point point = getAsPoint();
return point != null ? point.getX() : null;
}
}
@Entity
@Table(name = "map_markers")
public class MapMarker {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String title;
private String description;
@Column(columnDefinition = "geometry(Point,4326)")
@JsonIgnore
private Point location;
@Column(name = "icon_url")
private String iconUrl;
@Column(name = "icon_size_x")
private Integer iconSizeX = 25;
@Column(name = "icon_size_y")
private Integer iconSizeY = 41;
@Column(name = "icon_anchor_x")
private Integer iconAnchorX = 12;
@Column(name = "icon_anchor_y")
private Integer iconAnchorY = 41;
@Column(name = "popup_content")
private String popupContent;
@Column(name = "is_draggable")
private Boolean isDraggable = false;
@ElementCollection
@CollectionTable(name = "marker_properties", joinColumns = @JoinColumn(name = "marker_id"))
@MapKeyColumn(name = "property_key")
@Column(name = "property_value")
private Map<String, String> properties;
@Column(name = "created_at")
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
@PrePersist
protected void onCreate() {
createdAt = LocalDateTime.now();
updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
updatedAt = LocalDateTime.now();
}
// Getters and Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public Point getLocation() { return location; }
public void setLocation(Point location) { this.location = location; }
public String getIconUrl() { return iconUrl; }
public void setIconUrl(String iconUrl) { this.iconUrl = iconUrl; }
public Integer getIconSizeX() { return iconSizeX; }
public void setIconSizeX(Integer iconSizeX) { this.iconSizeX = iconSizeX; }
public Integer getIconSizeY() { return iconSizeY; }
public void setIconSizeY(Integer iconSizeY) { this.iconSizeY = iconSizeY; }
public Integer getIconAnchorX() { return iconAnchorX; }
public void setIconAnchorX(Integer iconAnchorX) { this.iconAnchorX = iconAnchorX; }
public Integer getIconAnchorY() { return iconAnchorY; }
public void setIconAnchorY(Integer iconAnchorY) { this.iconAnchorY = iconAnchorY; }
public String getPopupContent() { return popupContent; }
public void setPopupContent(String popupContent) { this.popupContent = popupContent; }
public Boolean getIsDraggable() { return isDraggable; }
public void setIsDraggable(Boolean isDraggable) { this.isDraggable = isDraggable; }
public Map<String, String> getProperties() { return properties; }
public void setProperties(Map<String, String> properties) { this.properties = properties; }
public LocalDateTime getCreatedAt() { return createdAt; }
public void setCreatedAt(LocalDateTime createdAt) { this.createdAt = createdAt; }
public LocalDateTime getUpdatedAt() { return updatedAt; }
public void setUpdatedAt(LocalDateTime updatedAt) { this.updatedAt = updatedAt; }
// Convenience methods
public Double getLatitude() {
return location != null ? location.getY() : null;
}
public Double getLongitude() {
return location != null ? location.getX() : null;
}
public void setLatLng(double lat, double lng) {
// This would typically use a geometry factory
// location = geometryFactory.createPoint(new Coordinate(lng, lat));
}
}
// Enums
public enum LayerType {
TILE("Tile Layer"),
GEOJSON("GeoJSON"),
WMS("WMS"),
WMTS("WMTS"),
VECTOR("Vector"),
HEATMAP("Heatmap"),
CLUSTER("Cluster");
private final String displayName;
LayerType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
public enum FeatureType {
POINT("Point"),
LINE("Line"),
POLYGON("Polygon"),
MULTIPOINT("MultiPoint"),
MULTILINE("MultiLine"),
MULTIPOLYGON("MultiPolygon"),
GEOMETRYCOLLECTION("GeometryCollection");
private final String displayName;
FeatureType(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
public enum DataFormat {
GEOJSON("GeoJSON"),
KML("KML"),
GPX("GPX"),
SHAPEFILE("Shapefile"),
WKT("WKT"),
CSV("CSV");
private final String displayName;
DataFormat(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
Tile and Bounding Box Models
package com.example.leaflet.model;
public class TileRequest {
private int x;
private int y;
private int z;
private String layer;
private String format = "png";
private Integer scale; // For retina/high-DPI displays
// Getters and Setters
public int getX() { return x; }
public void setX(int x) { this.x = x; }
public int getY() { return y; }
public void setY(int y) { this.y = y; }
public int getZ() { return z; }
public void setZ(int z) { this.z = z; }
public String getLayer() { return layer; }
public void setLayer(String layer) { this.layer = layer; }
public String getFormat() { return format; }
public void setFormat(String format) { this.format = format; }
public Integer getScale() { return scale; }
public void setScale(Integer scale) { this.scale = scale; }
}
public class BoundingBox {
private double minLng;
private double minLat;
private double maxLng;
private double maxLat;
public BoundingBox() {}
public BoundingBox(double minLng, double minLat, double maxLng, double maxLat) {
this.minLng = minLng;
this.minLat = minLat;
this.maxLng = maxLng;
this.maxLat = maxLat;
}
// Getters and Setters
public double getMinLng() { return minLng; }
public void setMinLng(double minLng) { this.minLng = minLng; }
public double getMinLat() { return minLat; }
public void setMinLat(double minLat) { this.minLat = minLat; }
public double getMaxLng() { return maxLng; }
public void setMaxLng(double maxLng) { this.maxLng = maxLng; }
public double getMaxLat() { return maxLat; }
public void setMaxLat(double maxLat) { this.maxLat = maxLat; }
public boolean contains(double lng, double lat) {
return lng >= minLng && lng <= maxLng && lat >= minLat && lat <= maxLat;
}
public double getWidth() {
return maxLng - minLng;
}
public double getHeight() {
return maxLat - minLat;
}
public Point getCenter() {
double centerLng = (minLng + maxLng) / 2.0;
double centerLat = (minLat + maxLat) / 2.0;
return new Point(centerLng, centerLat);
}
}
public class Point {
private double lng;
private double lat;
public Point() {}
public Point(double lng, double lat) {
this.lng = lng;
this.lat = lat;
}
// Getters and Setters
public double getLng() { return lng; }
public void setLng(double lng) { this.lng = lng; }
public double getLat() { return lat; }
public void setLat(double lat) { this.lat = lat; }
public double distanceTo(Point other) {
double earthRadius = 6371; // kilometers
double dLat = Math.toRadians(other.lat - this.lat);
double dLng = Math.toRadians(other.lng - this.lng);
double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.cos(Math.toRadians(this.lat)) * Math.cos(Math.toRadians(other.lat)) *
Math.sin(dLng/2) * Math.sin(dLng/2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return earthRadius * c;
}
}
public class MapViewState {
private Point center;
private Integer zoom;
private BoundingBox bounds;
private java.util.List<String> activeLayers;
// Getters and Setters
public Point getCenter() { return center; }
public void setCenter(Point center) { this.center = center; }
public Integer getZoom() { return zoom; }
public void setZoom(Integer zoom) { this.zoom = zoom; }
public BoundingBox getBounds() { return bounds; }
public void setBounds(BoundingBox bounds) { this.bounds = bounds; }
public java.util.List<String> getActiveLayers() { return activeLayers; }
public void setActiveLayers(java.util.List<String> activeLayers) { this.activeLayers = activeLayers; }
}
3. Tile Serving System
package com.example.leaflet.service;
import com.example.leaflet.model.TileRequest;
import com.example.leaflet.model.MapLayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.stereotype.Service;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
@Service
public class TileService {
private static final Logger log = LoggerFactory.getLogger(TileService.class);
@Autowired
private ResourceLoader resourceLoader;
@Autowired
private MapLayerService mapLayerService;
@Autowired
private FeatureService featureService;
private static final int TILE_SIZE = 256;
/**
* Get tile image for the given request
*/
@Cacheable(value = "tiles", key = "#request.x + '-' + #request.y + '-' + #request.z + '-' + #request.layer")
public byte[] getTile(TileRequest request) throws IOException {
log.debug("Generating tile: {}/{}/{} for layer: {}",
request.getZ(), request.getX(), request.getY(), request.getLayer());
MapLayer layer = mapLayerService.getLayerByName(request.getLayer());
if (layer == null) {
return generateErrorTile("Layer not found");
}
switch (layer.getType()) {
case TILE:
return serveExternalTile(request, layer);
case GEOJSON:
case VECTOR:
return generateVectorTile(request, layer);
case HEATMAP:
return generateHeatmapTile(request, layer);
default:
return generateErrorTile("Unsupported layer type");
}
}
/**
* Serve tile from external source (proxy)
*/
private byte[] serveExternalTile(TileRequest request, MapLayer layer) throws IOException {
String url = buildTileUrl(request, layer);
try {
Resource resource = resourceLoader.getResource(url);
if (resource.exists()) {
try (InputStream is = resource.getInputStream()) {
return readFully(is);
}
} else {
return generateNotFoundTile();
}
} catch (Exception e) {
log.warn("Failed to fetch external tile: {}", url, e);
return generateErrorTile("Tile unavailable");
}
}
/**
* Generate vector tile from spatial features
*/
private byte[] generateVectorTile(TileRequest request, MapLayer layer) throws IOException {
BufferedImage image = new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
try {
// Set up graphics
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Draw background (transparent)
g2d.setColor(new Color(0, 0, 0, 0));
g2d.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
// Get features for this tile bounds
BoundingBox bbox = tileToBoundingBox(request.getX(), request.getY(), request.getZ());
java.util.List<SpatialFeature> features = featureService.getFeaturesInBounds(layer, bbox);
// Draw features
for (SpatialFeature feature : features) {
drawFeature(g2d, feature, bbox, request.getZ());
}
// Convert to byte array
return imageToByteArray(image, request.getFormat());
} finally {
g2d.dispose();
}
}
/**
* Generate heatmap tile
*/
private byte[] generateHeatmapTile(TileRequest request, MapLayer layer) throws IOException {
BufferedImage image = new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
try {
// Create gradient for heatmap
GradientPaint gradient = new GradientPaint(
0, 0, new Color(0, 0, 255, 100), // Cool (low density)
TILE_SIZE, TILE_SIZE, new Color(255, 0, 0, 200) // Hot (high density)
);
g2d.setPaint(gradient);
g2d.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
// Add data points (simplified)
BoundingBox bbox = tileToBoundingBox(request.getX(), request.getY(), request.getZ());
java.util.List<SpatialFeature> features = featureService.getFeaturesInBounds(layer, bbox);
for (SpatialFeature feature : features) {
if (feature.getAsPoint() != null) {
Point point = feature.getAsPoint();
Point tilePoint = worldToTile(point.getLng(), point.getLat(), request.getZ(),
request.getX(), request.getY());
// Draw heat point
int radius = 20;
g2d.setColor(new Color(255, 255, 255, 100));
g2d.fillOval(
(int)(tilePoint.getLng() * TILE_SIZE) - radius/2,
(int)(tilePoint.getLat() * TILE_SIZE) - radius/2,
radius, radius
);
}
}
return imageToByteArray(image, request.getFormat());
} finally {
g2d.dispose();
}
}
/**
* Draw a spatial feature on the tile
*/
private void drawFeature(Graphics2D g2d, SpatialFeature feature, BoundingBox bbox, int zoom) {
// Apply feature style
applyFeatureStyle(g2d, feature.getStyle());
if (feature.getAsPoint() != null) {
drawPoint(g2d, feature.getAsPoint(), bbox, zoom);
} else if (feature.getAsPolygon() != null) {
drawPolygon(g2d, feature.getAsPolygon(), bbox, zoom);
}
// Add support for other geometry types...
}
/**
* Draw a point feature
*/
private void drawPoint(Graphics2D g2d, Point point, BoundingBox bbox, int zoom) {
Point tilePoint = worldToTile(point.getX(), point.getY(), bbox);
int markerSize = Math.max(5, 20 - zoom); // Adjust size based on zoom
g2d.fillOval(
(int)(tilePoint.getLng() * TILE_SIZE) - markerSize/2,
(int)(tilePoint.getLat() * TILE_SIZE) - markerSize/2,
markerSize, markerSize
);
}
/**
* Draw a polygon feature
*/
private void drawPolygon(Graphics2D g2d, Polygon polygon, BoundingBox bbox, int zoom) {
java.awt.Polygon awtPolygon = new java.awt.Polygon();
for (int i = 0; i < polygon.getNumPoints(); i++) {
Point point = new Point(
polygon.getCoordinates()[i].x,
polygon.getCoordinates()[i].y
);
Point tilePoint = worldToTile(point.getLng(), point.getLat(), bbox);
awtPolygon.addPoint(
(int)(tilePoint.getLng() * TILE_SIZE),
(int)(tilePoint.getLat() * TILE_SIZE)
);
}
g2d.fillPolygon(awtPolygon);
g2d.drawPolygon(awtPolygon);
}
/**
* Apply feature styling
*/
private void applyFeatureStyle(Graphics2D g2d, Map<String, String> style) {
if (style == null) return;
// Fill color
if (style.containsKey("fillColor")) {
g2d.setColor(parseColor(style.get("fillColor"), new Color(0, 0, 255, 128)));
}
// Stroke color
if (style.containsKey("color")) {
g2d.setColor(parseColor(style.get("color"), Color.BLACK));
}
// Stroke width
if (style.containsKey("weight")) {
g2d.setStroke(new BasicStroke(Float.parseFloat(style.get("weight"))));
}
}
/**
* Convert tile coordinates to bounding box
*/
public BoundingBox tileToBoundingBox(int x, int y, int zoom) {
double n = Math.pow(2, zoom);
double minLng = x / n * 360.0 - 180.0;
double maxLng = (x + 1) / n * 360.0 - 180.0;
double minLat = radToDeg(Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 1) / n))));
double maxLat = radToDeg(Math.atan(Math.sinh(Math.PI * (1 - 2 * y / n))));
return new BoundingBox(minLng, minLat, maxLng, maxLat);
}
/**
* Convert world coordinates to tile-relative coordinates
*/
private Point worldToTile(double lng, double lat, BoundingBox bbox) {
double x = (lng - bbox.getMinLng()) / bbox.getWidth();
double y = (bbox.getMaxLat() - lat) / bbox.getHeight(); // Invert Y axis
return new Point(x, y);
}
private Point worldToTile(double lng, double lat, int zoom, int tileX, int tileY) {
BoundingBox bbox = tileToBoundingBox(tileX, tileY, zoom);
return worldToTile(lng, lat, bbox);
}
/**
* Build external tile URL from template
*/
private String buildTileUrl(TileRequest request, MapLayer layer) {
String url = layer.getTileUrlTemplate()
.replace("{x}", String.valueOf(request.getX()))
.replace("{y}", String.valueOf(request.getY()))
.replace("{z}", String.valueOf(request.getZ()))
.replace("{s}", "a"); // Subdomain - could be randomized
if (request.getScale() != null && request.getScale() > 1) {
url = url.replace("{r}", "@2x");
} else {
url = url.replace("{r}", "");
}
return url;
}
/**
* Generate error tile
*/
private byte[] generateErrorTile(String message) throws IOException {
BufferedImage image = new BufferedImage(TILE_SIZE, TILE_SIZE, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
try {
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, TILE_SIZE, TILE_SIZE);
g2d.setColor(Color.RED);
g2d.drawString(message, 10, TILE_SIZE / 2);
return imageToByteArray(image, "png");
} finally {
g2d.dispose();
}
}
private byte[] generateNotFoundTile() throws IOException {
return generateErrorTile("Not Found");
}
/**
* Convert image to byte array
*/
private byte[] imageToByteArray(BufferedImage image, String format) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, format, baos);
return baos.toByteArray();
}
/**
* Read input stream fully
*/
private byte[] readFully(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) != -1) {
baos.write(buffer, 0, length);
}
return baos.toByteArray();
}
/**
* Parse color from string
*/
private Color parseColor(String colorStr, Color defaultColor) {
try {
if (colorStr.startsWith("#")) {
return Color.decode(colorStr);
} else if (colorStr.startsWith("rgb")) {
// Parse rgb(r, g, b) or rgba(r, g, b, a)
String[] parts = colorStr.replace("rgb(", "").replace("rgba(", "").replace(")", "").split(",");
int r = Integer.parseInt(parts[0].trim());
int g = Integer.parseInt(parts[1].trim());
int b = Integer.parseInt(parts[2].trim());
int a = parts.length > 3 ? (int)(Float.parseFloat(parts[3].trim()) * 255) : 255;
return new Color(r, g, b, a);
}
} catch (Exception e) {
log.warn("Failed to parse color: {}", colorStr, e);
}
return defaultColor;
}
private double radToDeg(double rad) {
return rad * 180.0 / Math.PI;
}
}
4. Geospatial Services
```java
package com.example.leaflet.service;
import com.example.leaflet.model.*;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.ge
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.