Линии на карте затмения не отображаются в масштабе на карте d3.js

У меня есть следующая карта США, на которой показаны прогнозируемые в настоящее время погодные условия для каждого округа в зоне затмения или рядом с ней во время затмения. Я хочу иметь возможность показать линии, обозначающие северную, южную и центральную линии зоны тотальности, но я не могу заставить их правильно масштабироваться.

Красная линия, которая показана, должна быть северной линией, но она не соответствует масштабу.

Карта погоды в зоне затмения

Вот код, есть идеи? Линия в самом низу

svg.append('path').datum(feature.geometry).attr('class', 'mine').attr("d", path2);

вот где я пытаюсь провести черту.

Спасибо

<!DOCTYPE html>
<meta charset="utf-8">
<style>

    .counties {
        fill: none;
        stroke: #ddd;
    }

    .states {
        fill: none;
        stroke: #000;
        stroke-linejoin: round;
    }

    .mine {
        fill: #f00;
        stroke: #f00;
        stroke-linejoin: round;
    }

</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v2.min.js"></script>
<script>

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height");

    var unemployment = d3.map();

    var path = d3.geoPath();
    var path2 = d3.geoPath();

    var x = d3.scaleLinear()
        .domain([1, 10])
        .rangeRound([600, 860]);

    var color = d3.scaleThreshold()
        .domain(d3.range(2, 10))
        .range(d3.schemeBlues[9]);

    var g = svg.append("g")
        .attr("class", "key")
        .attr("transform", "translate(0,40)");

    g.selectAll("rect")
        .data(color.range().map(function (d) {
            d = color.invertExtent(d);
            if (d[0] == null) d[0] = x.domain()[0];
            if (d[1] == null) d[1] = x.domain()[1];
            return d;
        }))
        .enter().append("rect")
        .attr("height", 8)
        .attr("x", function (d) {
            return x(d[0]);
        })
        .attr("width", function (d) {
            return x(d[1]) - x(d[0]);
        })
        .attr("fill", function (d) {
            return color(d[0]);
        });

    g.append("text")
        .attr("class", "caption")
        .attr("x", x.range()[0])
        .attr("y", -6)
        .attr("fill", "#000")
        .attr("text-anchor", "start")
        .attr("font-weight", "bold")
        .text("Forecast Conditions");

    g.call(d3.axisBottom(x)
        .tickSize(13)
        .tickFormat(function (x, i) {
            //return i ? x : x;
            if (i == 0)
                return "Clear";
            else if (i == 7)
                return "Rain";
            else
                return "";
        })
        .tickValues(color.domain()))
        .select(".domain")
        .remove();

    d3.queue()
        .defer(d3.json, "https://d3js.org/us-10m.v1.json")
        .defer(d3.tsv, "unemployment.tsv", function (d) {
            var forecast = {
                forecastNum: d.forecastNum,
                name: d.name,
                forecastText: d.forecastText
            };
            unemployment.set(d.id, forecast);
        })
        .await(ready);

    function ready(error, us) {
        if (error) throw error;

        var feature = {
            type: "Feature",
            properties: {},
            geometry: {
                type: "LineString",
                coordinates: [
                    [136.9522, 45.1172],
                    [36.8017, 13.6517],
                ]
            }
        };
        svg.append("g")
            .attr("class", "counties")
            .selectAll("path")
            .data(topojson.feature(us, us.objects.counties).features)
            .enter().append("path")
            .attr("fill", function (d) {
                return color(d.forecastNum = unemployment.get(d.id).forecastNum);
            })
            .attr("d", path)
            .append("title")
            .text(function (d) {
                var fc = unemployment.get(d.id);

                var s = fc.name + " " + fc.forecastText;
                console.log('[' + s + "]");
                return s;
            });

        svg.append("path")
            .datum(topojson.mesh(us, us.objects.states, function (a, b) {
                return a !== b;
            }))
            .attr("class", "states")
            .attr("d", path)
        ;
//        svg.append("circle").attr("r",50).attr("transform", function() {return "translate(" + projection([-75,43]) + ")";});
        svg.append('path').datum(feature.geometry).attr('class', 'mine').attr("d", path2);

    }

</script>

person JJF    schedule 16.08.2017    source источник


Ответы (1)


Ваша карта работает так, как ожидалось, но вы можете настроить ее так, как хотите.

Проблема

Этот файл: https://d3js.org/us-10m.v1.json уже проецируется (состоит из координат x,y на двумерной плоскости с произвольной единицей расстояния).

Ваша линия не проецируется (она состоит из пар долгота/широта, которые представляют собой точки на трехмерном глобусе).

В конечном счете, у вас есть объекты в двух разных системах координат, что вызывает проблемы, потому что вы наносите эти точки из двух разных систем координат одинаковым образом.

Определение того, проецируются ли географические данные в d3

Чтобы определить, спроецированы ли уже ваши данные, вы можете проверить:

  • использует ли geoPath нулевую проекцию?
  • являются ли пары координат действительными парами широты и долготы?

Глядя на этот файл, вы можете видеть, что геопути не назначена никакая проекция (это можно сделать так: d3.geoPath().projection(projection)).

Кроме того, вы можете видеть, что координаты topojson находятся примерно в пределах [0,0] и [960,600] (преобразуйте в geojson, чтобы увидеть простые координаты). Это недопустимые пары долготы/широты (такие как [+/-180,+/-90]).

Ваш линейный объект, однако, построен с парами долгота/широта. Эта функция не проецируется (при использовании определенного сфероида для представления Земли можно сказать, что он «проецируется» в WGS84, но на самом деле WGS84 в этом контексте просто представляет датум, а не проекцию. Для справки, WGS84 — это опорный/опорный сфероид, используемый d3 при преобразовании из длинных/широчайших в точки на плоскости).

Что происходит?

Нулевая проекция будет принимать координаты [x,y] и возвращать те же самые координаты. Поэтому быстро отдать уже спроецированные объекты можно, просмотрев объекты в средстве просмотра (mapshaper.org). Если объект перевернут, значит, у вас есть спроецированные данные. Это происходит из-за того, что координатное пространство svg помещает ноль вверху, а пары долгота/широта помещают ноль на экваторе, а -90 ниже.

При рисовании карты у вас есть линия, которую вы хотите нарисовать:

       coordinates: [
            [136.9522, 45.1172],
            [36.8017, 13.6517],
        ]

Поскольку вы используете нулевую проекцию, линия просто идет от точки на 45 пикселей вниз сверху и на 137 пикселей слева к точке на 13 пикселей вниз и на 37 пикселей слева. Кроме того, долгота в континентальной части США отрицательна, но, конечно, тогда она вообще не будет отображаться на вашей карте.

Решение

Вам необходимо использовать согласованные прогнозы для ваших данных. Для этого вы можете сделать одно из:

  1. Выясните проекцию, используемую для topojson, и преобразуйте свои координаты в эту проекцию, чтобы вы могли использовать преобразованные координаты в массиве координат вашей линии и «проецировать» линию с нулевой проекцией.

  2. Выясните проекцию, используемую для topojson, и эмулируйте ее с помощью d3.geoProjection, это решение будет использовать geoPath с нулевой проекцией для topojson (как в настоящее время) и geoPath с довольно специфической проекцией для преобразования строки в ту же самую. координатное пространство

  3. Отмените проекцию топоиска и используйте одну и ту же проекцию для обоих объектов (линии и топоиска).

  4. Найдите не спроецированный топожсон/геоджон/и т. д. США и используйте одну и ту же проекцию для обоих объектов (линии и топожсона).

Учитывая, что исходный файл, который у вас есть, вероятно, был создан Майком Бостоком, и он, вероятно, использовал ту же формулу проецирования для создания файла, которую он реализовал в d3, нам может повезти, и мы выясним, что d3.geoProjection мы могли бы использовать для проецирования наших координат на то же координатное пространство, что и топожсон (вариант 2 выше).

Проекция d3.geoAlbers по умолчанию сосредоточена на США, что является исключением по сравнению с большинством других проекций d3. Он может содержать по умолчанию ключевые параметры проекции, используемые для создания топоиска, а именно стандартные параллели, центральную точку и поворот.

Однако масштаб проекции d3 по умолчанию предполагает плоскость проекции 960 x 500. Перевод по умолчанию проекции d3 предполагает перевод [960/2,500/2]. Если мы изменим их, чтобы приспособить объект высотой 600 пикселей, нам повезет, и мы сможем спроецировать координаты в очень похожее координатное пространство, если не в то же самое координатное пространство:

var projection = d3.geoAlbers();
var scale = d3.geoAlbers().scale(); // get the default scale

projection.scale(scale * 1.2) // adjust for 600 pixels tall
  .translate([960/2,600/2]    // adjust for 600 pixels tall here too.

var geoPath = d3.geoPath.projection(projection)  // use the projection to draw features.

Это дает нам успешное перекрытие. Здесь показана непроецированная топология данных, наложенная на проецируемую топографию. Это позволяет нам достичь вашей цели, проецируя линию поверх непроецируемых объектов, см. здесь (у меня нет ваших данных tsv, поэтому хороплет — это просто шум).

Вы также можете масштабировать уже спроецированные данные, используя geoTransform, а не geoProjection, см. at-all/42430876#42430876">ответить. Это позволит вам выбрать размеры SVG, которые не требуют 960 на 600 пикселей для отображения всех функций.

Предупреждение о решении

К сожалению, варианты 1, 2 и 3 требуют, чтобы вы знали, как были спроецированы географические данные в топожсоне, чтобы вы могли эмулировать их или реверсировать. Если у вас нет этой информации, эти варианты невозможны. Это справедливо при работе с географическими данными, в которых используются разные системы пространственной привязки (SRS или системы координат, CRS).

В этих случаях остается вариант 4. Непрогнозируемый топожсон для Штатов можно найти здесь в этом блоке (вместе с хорошими параметрами проекции) . Вот необработанный топожсон. Вот демонстрация вашей линии (В этом топографическом документе нет страны ids, и, следовательно, разрушит ваш хлороплет).

person Andrew Reid    schedule 16.08.2017
comment
Спасибо, Эндрю. Перевариваю это сейчас. - person JJF; 16.08.2017
comment
Эндрю, если бы я знал, например, что топожсон был спроецирован с помощью d3.geoAlbersUsa, я бы тоже мог перевести свои координаты таким же образом, нет? Кроме того, сколькими различными способами это можно спроецировать? Разве я не мог просто попробовать, пока не нашел правильный? Очень очень новичок во всем этом BTW. - person JJF; 16.08.2017
comment
Если бы он был спроецирован с использованием тех же параметров, что и d3.geoAlbersUsa, то проецирование точки с этой проекцией могло бы привести к выравниванию (кажется, я пробовал это однажды безуспешно, но я мог ошибаться). Если вы не можете выровнять их таким образом, то можно много гадать, учитывая, что альберс построен с координатами центрирования и вращения и двумя стандартными параллелями, а также с масштабом. - person Andrew Reid; 16.08.2017
comment
Я думаю, что я очень точно выровнял проекции, используя d3 albers (albersUsa может сработать, но аспект составной проекции может иметь непредвиденные последствия, если конечная точка находится ближе к Аляске, чем к континентальной части США). - person Andrew Reid; 16.08.2017
comment
Все еще перевариваю ... большое спасибо за ваши усилия - person JJF; 16.08.2017
comment
Глядя на результат и другие карты затмений, я подумал, что быстро добавлю: две выбранные вами точки не создают очень репрезентативной линии, поскольку затмения не движутся по большим кругам, как d3 geoPath. Кривизна Альберса, кажется, усугубляет это. Вместо этого вы можете использовать более подробный геоджсон для траектории. Взгляните на этот блок. Необработанный geojson здесь. - person Andrew Reid; 17.08.2017
comment
Кроме того, d3.geoAlbersUsa тоже будет нормально работать с этими данными, он не изменит внешний вид того, что там есть, но позволит спроецировать точки на Аляску или Гавайи. - person Andrew Reid; 17.08.2017
comment
Спасибо, Эндрю. У меня гораздо больше очков от НАСА, около 90. Я думаю, это должно заполнить их, не так ли? - person JJF; 17.08.2017
comment
Должен работать, если это не сработает, geojson в моем комментарии выше имеет смехотворное количество баллов за то, что нужно. - person Andrew Reid; 17.08.2017