用 d3 将测地线钳制到 min/max 纬度
Clamp geodesic to min/max latitude with d3
我正在使用 d3 在地图上绘制路线:
const width = 400;
const height = width / 2;
const projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.pi);
const route_projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.pi);
//.preclip(SOME CLIPPING FUNCTION)
const route_path = d3.geoPath()
.projection(route_projection);
const path = d3.geoPath()
.projection(projection);
const zoom = d3.zoom()
.extent([
[0, 0],
[width, height]
])
.translateExtent([
[0, 0],
[width, height]
])
.scaleExtent([1, Infinity])
.on("zoom", zoomed);
let svg = d3.select('#map')
.append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoom);
svg.append("rect")
.attr("class", "background")
.attr("fill", "#b4dcfc")
.attr("width", width)
.attr("height", height);
let g = svg.append('g');
let geojson = {}
let curve = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"population": 200
},
"geometry": {
"type": "LineString",
"coordinates": [
[-56.12, -35.50],
[47.81, -25.37],
[78.71, 7.73]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-56.12, -35.50]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [47.81, -25.37]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [78.71, 7.73]
},
"properties": {
"name": "Dinagat Islands"
}
}
]
}
function createMap() {
projection.fitSize([width, height], geojson);
route_projection.fitSize([width, height], geojson);
g
.attr("id", "dmas")
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("fill", "white")
.attr("stroke", "#222")
.attr("stroke-width", 1)
.attr("vector-effect", "non-scaling-stroke")
.attr("d", path);
g.append("path")
.datum(curve)
.attr("id", "route")
.attr("d", route_path)
.attr("fill", "none")
.attr("stroke", 'red')
.attr("stroke-width", 2)
.attr("vector-effect", "non-scaling-stroke")
.attr("stroke-opacity", 1);
}
function zoomed(transform) {
g
.selectAll('path')
.attr('transform', transform.transform)
// .attr('stroke-width', 2/transform.transform.k)
}
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json")
.then(function(data) {
geojson = topojson.feature(data, data.objects.countries);
createMap();
})
<script src="https://unpkg.com/d3-geo-polygon@1.12.1/dist/d3-geo-polygon.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="map"></div>
在 route_projection
,我想使用预剪辑将红色路线的最大纬度限制在 -40 度纬度以上,这样它最终会成为从南美洲到南非的直线.
像这样:
我该怎么做?
裁剪函数不会达到正确的效果,因为它会裁剪您的功能而不是强制它们遵循该线。
相反,我们可以将您的投影与地理变换相结合,以强制线条遵守投影 space:
内的规则
const limit = d3.geoTransform({
point: function(x,y) {
this.stream.point(x, Math.min(y, projection([0,-36])[1]));
}
});
const route_projection = {
stream: function(s) {
return projection.stream(limit.stream(s));
}
};
const route_path = d3.geoPath()
.projection(route_projection);
这意味着我们只需要一个等距柱状投影而不是两个,我们只需将它与 geoTransform 流组合并将组合流传递给第二个路径生成器:
const width = 400;
const height = width / 2;
const projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.pi);
const limit = d3.geoTransform({
point: function(x,y) {
this.stream.point(x, Math.min(y, projection([0,-36])[1]));
}
});
const route_projection = {
stream: function(s) {
return projection.stream(limit.stream(s));
}
};
const route_path = d3.geoPath()
.projection(route_projection);
const path = d3.geoPath()
.projection(projection);
const zoom = d3.zoom()
.extent([
[0, 0],
[width, height]
])
.translateExtent([
[0, 0],
[width, height]
])
.scaleExtent([1, Infinity])
.on("zoom", zoomed);
let svg = d3.select('#map')
.append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoom);
svg.append("rect")
.attr("class", "background")
.attr("fill", "#b4dcfc")
.attr("width", width)
.attr("height", height);
let g = svg.append('g');
let geojson = {}
let curve = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"population": 200
},
"geometry": {
"type": "LineString",
"coordinates": [
[-56.12, -35.50],
[47.81, -25.37],
[78.71, 7.73]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-56.12, -35.50]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [47.81, -25.37]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [78.71, 7.73]
},
"properties": {
"name": "Dinagat Islands"
}
}
]
}
function createMap() {
projection.fitSize([width, height], geojson);
g
.attr("id", "dmas")
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("fill", "white")
.attr("stroke", "#222")
.attr("stroke-width", 1)
.attr("vector-effect", "non-scaling-stroke")
.attr("d", path);
g.append("path")
.datum(curve)
.attr("id", "route")
.attr("d", route_path)
.attr("fill", "none")
.attr("stroke", 'red')
.attr("stroke-width", 2)
.attr("vector-effect", "non-scaling-stroke")
.attr("stroke-opacity", 1);
}
function zoomed(transform) {
g
.selectAll('path')
.attr('transform', transform.transform)
}
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json")
.then(function(data) {
geojson = topojson.feature(data, data.objects.countries);
createMap();
})
<script src="https://unpkg.com/d3-geo-polygon@1.12.1/dist/d3-geo-polygon.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="map"></div>
哪个应该给出:
我正在使用 d3 在地图上绘制路线:
const width = 400;
const height = width / 2;
const projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.pi);
const route_projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.pi);
//.preclip(SOME CLIPPING FUNCTION)
const route_path = d3.geoPath()
.projection(route_projection);
const path = d3.geoPath()
.projection(projection);
const zoom = d3.zoom()
.extent([
[0, 0],
[width, height]
])
.translateExtent([
[0, 0],
[width, height]
])
.scaleExtent([1, Infinity])
.on("zoom", zoomed);
let svg = d3.select('#map')
.append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoom);
svg.append("rect")
.attr("class", "background")
.attr("fill", "#b4dcfc")
.attr("width", width)
.attr("height", height);
let g = svg.append('g');
let geojson = {}
let curve = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"population": 200
},
"geometry": {
"type": "LineString",
"coordinates": [
[-56.12, -35.50],
[47.81, -25.37],
[78.71, 7.73]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-56.12, -35.50]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [47.81, -25.37]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [78.71, 7.73]
},
"properties": {
"name": "Dinagat Islands"
}
}
]
}
function createMap() {
projection.fitSize([width, height], geojson);
route_projection.fitSize([width, height], geojson);
g
.attr("id", "dmas")
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("fill", "white")
.attr("stroke", "#222")
.attr("stroke-width", 1)
.attr("vector-effect", "non-scaling-stroke")
.attr("d", path);
g.append("path")
.datum(curve)
.attr("id", "route")
.attr("d", route_path)
.attr("fill", "none")
.attr("stroke", 'red')
.attr("stroke-width", 2)
.attr("vector-effect", "non-scaling-stroke")
.attr("stroke-opacity", 1);
}
function zoomed(transform) {
g
.selectAll('path')
.attr('transform', transform.transform)
// .attr('stroke-width', 2/transform.transform.k)
}
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json")
.then(function(data) {
geojson = topojson.feature(data, data.objects.countries);
createMap();
})
<script src="https://unpkg.com/d3-geo-polygon@1.12.1/dist/d3-geo-polygon.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="map"></div>
在 route_projection
,我想使用预剪辑将红色路线的最大纬度限制在 -40 度纬度以上,这样它最终会成为从南美洲到南非的直线.
像这样:
我该怎么做?
裁剪函数不会达到正确的效果,因为它会裁剪您的功能而不是强制它们遵循该线。
相反,我们可以将您的投影与地理变换相结合,以强制线条遵守投影 space:
内的规则const limit = d3.geoTransform({
point: function(x,y) {
this.stream.point(x, Math.min(y, projection([0,-36])[1]));
}
});
const route_projection = {
stream: function(s) {
return projection.stream(limit.stream(s));
}
};
const route_path = d3.geoPath()
.projection(route_projection);
这意味着我们只需要一个等距柱状投影而不是两个,我们只需将它与 geoTransform 流组合并将组合流传递给第二个路径生成器:
const width = 400;
const height = width / 2;
const projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale((width - 1) / 2 / Math.pi);
const limit = d3.geoTransform({
point: function(x,y) {
this.stream.point(x, Math.min(y, projection([0,-36])[1]));
}
});
const route_projection = {
stream: function(s) {
return projection.stream(limit.stream(s));
}
};
const route_path = d3.geoPath()
.projection(route_projection);
const path = d3.geoPath()
.projection(projection);
const zoom = d3.zoom()
.extent([
[0, 0],
[width, height]
])
.translateExtent([
[0, 0],
[width, height]
])
.scaleExtent([1, Infinity])
.on("zoom", zoomed);
let svg = d3.select('#map')
.append("svg")
.attr("width", width)
.attr("height", height);
svg.call(zoom);
svg.append("rect")
.attr("class", "background")
.attr("fill", "#b4dcfc")
.attr("width", width)
.attr("height", height);
let g = svg.append('g');
let geojson = {}
let curve = {
"type": "FeatureCollection",
"features": [{
"type": "Feature",
"properties": {
"population": 200
},
"geometry": {
"type": "LineString",
"coordinates": [
[-56.12, -35.50],
[47.81, -25.37],
[78.71, 7.73]
]
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [-56.12, -35.50]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [47.81, -25.37]
},
"properties": {
"name": "Dinagat Islands"
}
},
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [78.71, 7.73]
},
"properties": {
"name": "Dinagat Islands"
}
}
]
}
function createMap() {
projection.fitSize([width, height], geojson);
g
.attr("id", "dmas")
.selectAll("path")
.data(geojson.features)
.enter()
.append("path")
.attr("fill", "white")
.attr("stroke", "#222")
.attr("stroke-width", 1)
.attr("vector-effect", "non-scaling-stroke")
.attr("d", path);
g.append("path")
.datum(curve)
.attr("id", "route")
.attr("d", route_path)
.attr("fill", "none")
.attr("stroke", 'red')
.attr("stroke-width", 2)
.attr("vector-effect", "non-scaling-stroke")
.attr("stroke-opacity", 1);
}
function zoomed(transform) {
g
.selectAll('path')
.attr('transform', transform.transform)
}
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-50m.json")
.then(function(data) {
geojson = topojson.feature(data, data.objects.countries);
createMap();
})
<script src="https://unpkg.com/d3-geo-polygon@1.12.1/dist/d3-geo-polygon.min.js"></script>
<script src="https://unpkg.com/topojson@3.0.2/dist/topojson.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.2.0/d3.min.js"></script>
<div id="map"></div>
哪个应该给出: