class d3PointLayer extends ol.layer.Layer {
constructor(options) {
super(options);
this.data = options.data;
this.extent = options.extent;
this.initial_zoom = options.initial_zoom;
this.fill = options.fill;
this.fillOpacity = options.fillOpacity;
this.stroke = options.stroke;
this.strokeWidth = options.strokeWidth;
this.r0 = options.r0;
this.b = options.r_zoom_factor;
this.pointerenter = options.pointerenter;
this.pointerleave = options.pointerleave;
this.svg = d3
.select(document.createElement("div"))
.append("svg")
.style("position", "absolute");
}
getSourceState() {
return "ready";
}
render(frameState) {
const width = frameState.size[0];
const height = frameState.size[1];
const projection = frameState.viewState.projection;
const d3Projection = d3.geoMercator().scale(1).translate([0, 0]);
let d3Path = d3.geoPath().projection(d3Projection);
const geoBoundsLeftBottom = ol.proj.fromLonLat(this.extent[0], projection);
const geoBoundsRightTop = ol.proj.fromLonLat(this.extent[1], projection);
const geoBoundsWidth = geoBoundsRightTop[0] - geoBoundsLeftBottom[0];
if (geoBoundsWidth < 0) {
geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
}
const geoBoundsHeight = geoBoundsRightTop[1] - geoBoundsLeftBottom[1];
let pixel_lower_left = d3Projection(this.extent[0]);
let pixel_upper_right = d3Projection(this.extent[1]);
let pixelBoundsWidth = pixel_upper_right[0] - pixel_lower_left[0];
let pixelBoundsHeight = pixel_lower_left[1] - pixel_upper_right[1];
const widthResolution = geoBoundsWidth / pixelBoundsWidth;
const heightResolution = geoBoundsHeight / pixelBoundsHeight;
const r = Math.max(widthResolution, heightResolution);
const scale = r / frameState.viewState.resolution;
const center = ol.proj.toLonLat(
ol.extent.getCenter(frameState.extent),
projection
);
const angle = (-frameState.viewState.rotation * 180) / Math.PI;
d3Projection
.scale(scale)
.center(center)
.translate([width / 2, height / 2])
.angle(angle);
this.svg.attr("width", width);
this.svg.attr("height", height);
this.data.forEach(function (pt) {
let [x, y] = d3Projection([pt.lon, pt.lat]);
pt.x = x;
pt.y = y;
});
this.svg.selectAll("circle").remove();
let b;
if (typeof this.b == "number") {
b = this.b;
} else {
b = 1.414;
}
let z0 = this.initial_zoom;
let z = frameState.viewState.zoom;
let fill = this.fill || "#555";
let stroke = this.stroke || "black";
let strokeWidth;
if (this.strokeWidth === 0) {
strokeWidth = 0;
} else {
strokeWidth = this.strokeWidth || 1;
}
let fillOpacity;
if (this.fillOpacity === 0) {
fillOpacity = 0;
} else {
fillOpacity = this.fillOpacity || 0.8;
}
let r0 = this.r0;
function R(d) {
if (typeof r0 == "number") {
return r0 * b ** (z - z0);
} else if (typeof r0 == "function") {
return r0(d) * b ** (z - z0);
} else {
return 3 * b ** (z - z0);
}
}
let pts = this.svg.append("g");
pts
.selectAll("circle")
.data(this.data)
.join("circle")
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", R)
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("fill-opacity", fillOpacity);
if (this.pointerenter) {
pts.selectAll("circle").on("pointerenter", this.pointerenter);
}
if (this.pointerleave) {
pts.selectAll("circle").on("mouseleave", this.pointerleave);
}
return this.svg.node();
}
}