Что касается метода тестирования для определения правильности расположения объектов, попробуйте легко определить ориентиры, я использую Сиэтл и Майами ниже - они находятся на противоположных сторонах области интереса, и это должно бытьЛегко сказать, находятся ли они не в том месте (в воде или на суше).
Я не уверен, где они должны упасть, поскольку у меня нет координат, но я могу сказать вам,они не там, где должны быть.
Причина, по которой я могу это знать, заключается в том, что вы используете две разные проекции для своих данных.
Проекция Меркатора
Вы определяете одну из проекций и используете ее для позиционирования точек:
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
Это проекция Меркатора с центром в [0 °, 0 °] (по умолчанию).Вот мир, спроектированный с этой проекцией (с полем и SVG такого же размера):
D3 GᴇᴏMᴇʀᴄᴀᴛᴏʀ Wɪᴛʜ Cᴇɴᴛᴇʀ [0,0] ᴀɴᴅ Sᴄᴀʟᴇ200
Вы проецируете координаты для кругов на основе этой проекции.
Для воспроизводимости, вот фрагмент - вы должны просмотреть в полноэкранном режиме:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("class", "states")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(json, json.objects.land).features)
.enter().append("path")
.attr("d", path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
Таинственная проекция
Вторая проекция не очевидна.Если вы посмотрите на фрагмент, использованный для создания приведенного выше изображения, вы заметите, что он назначает проекцию пути:
var path = d3.geoPath().projection(projection);
Это так, что путь преобразует каждую географическую координату (сферическую широту / долготупару) к правильной координате на экране (декартово значение x, y пикселя): координата [-17 °, 85 °] будет преобразована во что-то вроде [100px, 50px].
В вашемвопрос, который вы просто используете:
var path = d3.geoPath();
Вы не назначаете проекцию пути - поэтому d3.geoPath () просто строит каждую вершину / точку в геойсоне / топойсоне так, как если бы координата содержала координаты пикселей:координаты [100px, 50px] в геойсоне / топойсоне нанесены на SVG при x = 100, y = 50.
Несмотря на то, что вы не используете проекцию, ваш график в штатах США соответствует ожидаемому.Почему?Потому что геойсон / топойсон был уже спроектирован.Поскольку он был предварительно спроектирован, нам не нужно использовать проекцию при построении графика с помощью D3.
Предварительно спроецированная геометрия может быть полезна, поскольку для ее рисования требуется меньше вычислений, что приводит к более высокой скорости рендеринга, но при этом требуется меньшая гибкость (см. здесь ).
Если мы наложим вашу предварительно спроецированную геометрию на геометрию, которую вы проецируете с помощью d3.geoProjection, мы получим:
Естественно, вы можете увидеть тамнет смысла, что то же самое между двумя.Следовательно, вы не проецируете точки, чтобы они правильно перекрывали предварительно спроецированные геометрии.
Cᴏᴍᴘᴀʀɪsᴏɴ ʙᴇᴛᴡᴇᴇɴ ᴛʜᴇ ᴛᴡᴏ ᴘʀᴏᴊᴇᴄᴛɪᴏɴs
Фрагмент для воспроизведения:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(json, json.objects.land).features)
.enter().append("path")
.attr("d", path);
})
d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {
var path = d3.geoPath();
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
Неудовлетворительное решение
Без метаданных, объясняющих, какую проекцию и систему координат использует геойсон / топойсон, мы обычно не можем дублировать эту проекцию для наложения других функций.
В этом случае, однако, если мы внимательно посмотрим на нанесенные на карту штаты США, то увидим, что проекция Альберса использовалась для предварительного проектирования контуров штатов.
Иногда мы можем угадать параметры проекции.Поскольку я довольно знаком с этим файлом (), я могу сказать, что он использует следующие параметры:
d3.geoAlbersUsa()
.scale(d3.geoAlbersUsa().scale()*6/5)
.translate([480,300]);
Вот пример, показывающий перекрытие Майами и Сиэтла:
var width = 960,height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {
var path = d3.geoPath();
var projection = d3.geoAlbersUsa()
.scale(d3.geoAlbersUsa().scale()*6/5)
.translate([width/2,height/2]);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
var places = [
[-122.3367534,47.5996582],
[-80.1942949,25.7645783]
]
svg.selectAll(null)
.data(places)
.enter()
.append("circle")
.attr("r", 3)
.attr("transform", function(d) {
return "translate("+projection(d)+")";
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
Но у этого есть обратная сторона: он очень тупой в усыновлении.Например, для экранов других размеров, трансляций, центров, масштабов и т. д. Предварительно спроектированная геометрия также создает много путаницы в сочетании с непроектированной геометрией.Например, этот вопрос демонстрирует общее разочарование по поводу правильного определения размера и центрирования предварительно спроецированной геометрии.
Лучшее решение
Лучшее решение - использовать один прогноз для всего.Либо сначала спроектируйте все сначала (что немного сложнее), либо спроецируйте все на лету (для браузера это действительно не займет много времени).Это становится понятнее и проще при изменении визуализации или географических данных.
Чтобы проецировать все одинаково, вам необходимо убедиться, что все ваши данные не спроецированы, то есть используются широта / долготапары для его координат / координатного пространства.Поскольку ваш американский JSON уже спроектирован, нам нужно найти другое, возможно:
И мы просто пропустим все через проекцию:
Фрагмент не будет загружать ресурс, но здесь a bl.ock с кодом, показанным ниже:
var width =960,height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
d3.json("us.json").then(function(us) {
var projection = d3.geoAlbersUsa()
.scale(150)
.translate([width/2,height/2]);
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
var places = [
[-122.3367534,47.5996582],
[-80.1942949,25.7645783]
]
svg.selectAll(null)
.data(places)
.enter()
.append("circle")
.attr("r", 3)
.attr("transform", function(d) {
return "translate("+projection(d)+")";
})
})