DevGang
Авторизоваться

Как создать тестовые данные GPS в Go

Многие функции, над которыми я работаю, используют массивы точек (GPS-треков) []float64{lng,lat} для статистического анализа. Отдельные треки могут содержать более 50 000 точек, описывающих реальное путешествие из пункта А в пункт Б.

Тестирование функций, которые обрабатывают GPS-треки, оказалось неожиданно сложным. Тестовые данные вида [1.0,2.0] для логического тестирования подходят. Но помимо этого, я хочу иметь возможность проверять согласованность в таких вещах, как поиск кластеров или контрольных точек коэффициента.

Для тестирования меня не интересует, где находятся эти места на земле, но удобно иметь возможность просматривать треки на карте для визуального подтверждения. Так что координаты должны как бы согласовываться.

Я создал функцию, которая генерирует полусвязные треки с данными GPS о местоположении. Создать дорожку из 5000 входных данных, предназначенную для тестирования чего-то конкретного, несложно. Возможность экспортировать ее в виде GeoJSON и просматривать форму на карте полезна для быстрой проверки интуиции.

В тестовой функции я могу настроить так, чтобы данные были искажены в ту или иную сторону, или вставить кластер странностей. Особенно полезно при больших объемах данных, когда один метод анализа может быть лучше другого.

func TestAnomalyDetection(t *testing.T) {

        line := orb.LineString{orb.Point{-3.188267, 55.953251}}

        bearingRange := [2]int{Direction_SSE, Direction_SSW}
        distanceRange := [2]int{10 * 1000, 15 * 1000}

        // generate a test GPS track
        for range 5000 {
                newPoint := generateNewLocation(line[len(line)-1],
                        bearingRange,
                        distanceRange)
                line = append(line, newPoint)
        }

        // add skewness in the data
        bearingRange = [2]int{Direction_W, Direction_WNW}
        distanceRange = [2]int{1000, 1500}
        for range 100 {
                newPoint := generateNewLocation(line[len(line)-1],
                        bearingRange,
                        distanceRange)
                line = append(line, newPoint)
        }

        // do testing
}

Я оставил следующий код как документированную единственную функцию, чтобы сделать его более читабельным. И я использовал пакет orb, чтобы скрыть множество наиболее распространенных вычислений и типов (orb).

Что, по-видимому, не является слишком распространенным явлением, так это создание новой точки на некотором расстоянии в определенном направлении. Противоположном point.DistanceTo(point2). На самом деле это все, что делает следующая функция.

// the compass rose, naming format for readability
const (
        Direction_N = iota
        Direction_NNE
        Direction_NE
        Direction_ENE
        Direction_E
        Direction_ESE
        Direction_SE
        Direction_SSE
        Direction_S
        Direction_SSW
        Direction_SW
        Direction_WSW
        Direction_W
        Direction_WNW
        Direction_NW
        Direction_NNW
)

const (
        compassRoseDegrees = 22.5
)

// generateNewLocation returns a new point in the range of direction and
// distance. It is meant to build non-repetitive but predictable GPS tracks, to
// help generate test input cases.
//
// It's also meant to be readable code.
func generateNewLocation(start orb.Point, direction [2]int, distance [2]int) orb.Point {

        // Mistakes with lon/lat indexing area easy to make, explicit index names
        // helps
        const (
                Longitude = 0
                Latitude  = 1
        )

        var (
                latitudeOneDegreeOfDistance = 111000  // metres
                newPoint                    orb.Point // []float64{Long, Lat}

                // convert from degrees to radians
                deg2rad = func(d float64) float64 { return d * math.Pi / 180 }
        )

        // Use trigonometry of a right angled triangle to solve the distances on the ground.
        // The hypotenuse is our desired distance to travel,  and one angle
        // is our desired bearing.
        //
        // now work out the vertical (longitude) and horizontal (latitude) sides in
        // distance units.
        hyp := (rand.Float64() * float64(distance[1]-distance[0])) + float64(distance[0])

        // Get the compass bearing in degrees, with a little randomness between the
        // general direction. Non-linear tracks are easier to troubleshoot visually.
        bearingMin := float64(direction[0]) * 22.5
        bearingMax := float64(direction[1]) * 22.5
        angle := (rand.Float64() * (bearingMax - bearingMin)) + bearingMin

        // Calulate the other side lengths using SOH CAH TOA. The Go math package
        // works in radians
        adj := math.Cos(deg2rad(angle)) * hyp // adjacent side of angle
        opp := math.Sin(deg2rad(angle)) * hyp // opposite side of angle

        // Each degree change in every latitude equates to ~111 km on the ground. So
        // now find the degree change required for the length of adj
        latitudeDelta := (1.0 / float64(latitudeOneDegreeOfDistance)) * adj
        newPoint[Latitude] = start[Latitude] + latitudeDelta

        // Distance on the ground for each degree of longitude changes depending on
        // latitude because the earth is not perfectly spherical. So we need to
        // calculate the distance of one degree longitude for our current latitude.
        p1 := orb.Point{1.0, start[Latitude]}
        p2 := orb.Point{2.0, start[Latitude]}
        longitudeOneDegreeOfDistance := geo.Distance(p1, p2) // returns metres

        // Now we can use this value to calculate the longitude degree change
        // required to move opp distance (in a horizontal straight line) at this
        // latitude.
        longitudeDelta := (1.0 / longitudeOneDegreeOfDistance) * opp

        // The new point is a vertical and horizontal shift to arrive at hyp
        // distance from the start point on the required bearing.
        newPoint[Longitude] = start[Longitude] + longitudeDelta

        return newPoint
}

Для вывода объекта geoJSON, который можно просмотреть с помощью средства просмотра Mapbox.

        fc := geojson.NewFeatureCollection()
        f := geojson.NewFeature(line)
        fc.Append(f)

        rawJSON, _ := fc.MarshalJSON()
        fmt.Println(string(rawJSON))

Источник:

#Golang #Data Science
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу