Skip to content

Commit

Permalink
Merge pull request #176 from vsimakhin/feature/different-airport-db-s…
Browse files Browse the repository at this point in the history
…ources

Feature/different airport db sources
  • Loading branch information
vsimakhin authored Jan 15, 2024
2 parents 4b46828 + c2536fa commit 7496da0
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 68 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [Unreleased]

- New: Additional sources for the airport databases. The new option added is from [https://ourairports.com](https://ourairports.com)

## [2.28.1] - 06.01.2024

- Fix: Cross Country hours in the Stats tables when there are no flights for the time period
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ So in real life the logbook could look like
![Licensing record](https://github.com/vsimakhin/web-logbook-assets/raw/main/licensing-record.png)
# Airports Databases
The app supports 3 sources:
* https://github.com/mwgg/Airports/raw/master/airports.json - main JSON database of 28k+ airports.
* (default) https://github.com/vsimakhin/Airports/raw/master/airports.json - my local fork of the main JSON database, to ensure that the app remains functional even if there are any breaking changes in the upstream.
* https://davidmegginson.github.io/ourairports-data/airports.csv - an alternate set of airports from https://ourairports.com/, which contains over 78k records, including small airfields and heliports.
If you enable the `No ICAO codes filter` option, the app will ignore ICAO airport codes that contain numbers and dashes, which are not commonly used ICAO codes. By default, this option is unchecked, which makes the database slightly smaller and cleaner.
Please make sure to click the `Save` button before updating the database to ensure that all changes are saved.
# HTTPS enable
Since the app is running on `localhost` it's not possible to create a public certificate that would be valid by public CAs. As an option, you can create a self-signed certificate and add it to the root CA in your operating system. For that, you can use [`mkcert` tool](https://github.com/FiloSottile/mkcert).
Expand Down
141 changes: 92 additions & 49 deletions cmd/web/handlers_airport.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package main

import (
"encoding/csv"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"

"github.com/go-chi/chi/v5"
"github.com/vsimakhin/web-logbook/internal/models"
Expand Down Expand Up @@ -33,86 +36,126 @@ func (app *application) HandlerAirportByID(w http.ResponseWriter, r *http.Reques
}
}

// HandlerAirportUpdate updates the Airports DB
func (app *application) HandlerAirportUpdate(w http.ResponseWriter, r *http.Request) {

var airportsDB map[string]interface{}
func (app *application) downloadAirportDB(source string) ([]models.Airport, error) {
var airports []models.Airport
var response models.JSONResponse
var airportsDB map[string]interface{}

// download the json db from the repo
resp, err := http.Get("https://github.com/vsimakhin/Airports/raw/master/airports.json")
if source == "" {
source = "https://github.com/vsimakhin/Airports/raw/master/airports.json" // default one
}

resp, err := http.Get(source)
if err != nil {
app.errorLog.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
return airports, err
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
app.errorLog.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
return airports, err
}

// parse the json
err = json.Unmarshal(body, &airportsDB)
if err != nil {
app.errorLog.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if strings.Contains(source, "airports.json") {
// let's assume it's a standard airport database
// however, it's not a good idea to rely on the file name, so might be changed in the future

for _, item := range airportsDB {
airportItem := item.(map[string]interface{})
// parse the json
err = json.Unmarshal(body, &airportsDB)
if err != nil {
return airports, err
}

var airport models.Airport
for _, item := range airportsDB {
airportItem := item.(map[string]interface{})

if value, ok := airportItem["icao"].(string); ok {
airport.ICAO = value
}
var airport models.Airport

if value, ok := airportItem["iata"].(string); ok {
airport.IATA = value
}
if value, ok := airportItem["icao"].(string); ok {
airport.ICAO = value
}

if value, ok := airportItem["name"].(string); ok {
airport.Name = value
}
if value, ok := airportItem["iata"].(string); ok {
airport.IATA = value
}

if value, ok := airportItem["city"].(string); ok {
airport.City = value
}
if value, ok := airportItem["name"].(string); ok {
airport.Name = value
}

if value, ok := airportItem["country"].(string); ok {
airport.Country = value
}
if value, ok := airportItem["city"].(string); ok {
airport.City = value
}

if value, ok := airportItem["elevation"].(float64); ok {
airport.Elevation = int(value)
}
if value, ok := airportItem["country"].(string); ok {
airport.Country = value
}

if value, ok := airportItem["lat"].(float64); ok {
airport.Lat = value
}
if value, ok := airportItem["elevation"].(float64); ok {
airport.Elevation = int(value)
}

if value, ok := airportItem["lat"].(float64); ok {
airport.Lat = value
}

if value, ok := airportItem["lon"].(float64); ok {
airport.Lon = value
}

if value, ok := airportItem["lon"].(float64); ok {
airport.Lon = value
airports = append(airports, airport)
}
} else {
// it's a new csv Ourairports source
r := csv.NewReader(strings.NewReader(string(body)))
for {
record, err := r.Read()
if err == io.EOF {
break
}
if err != nil {
app.errorLog.Println(err)
}

var airport models.Airport

airport.ICAO = record[1]
airport.IATA = record[13]
airport.Name = record[3]
airport.City = record[10]
airport.Country = record[8]
airport.Elevation, _ = strconv.Atoi(record[6])
airport.Lat, _ = strconv.ParseFloat(record[4], 64)
airport.Lon, _ = strconv.ParseFloat(record[5], 64)

airports = append(airports, airport)
}
}

return airports, nil
}

airports = append(airports, airport)
// HandlerAirportUpdate updates the Airports DB
func (app *application) HandlerAirportUpdate(w http.ResponseWriter, r *http.Request) {

var response models.JSONResponse

settings, err := app.db.GetSettings()
if err != nil {
app.errorLog.Println(err)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

records, err := app.db.UpdateAirportDB(airports)
airports, err := app.downloadAirportDB(settings.AirportDBSource)

records, err := app.db.UpdateAirportDB(airports, settings.NoICAOFilter)
if err != nil {
app.errorLog.Println(err)
response.OK = false
response.Message = err.Error()
} else {
response.OK = true
response.Message = fmt.Sprintf("%d", records)

}

err = app.writeJSON(w, http.StatusOK, response)
Expand Down
102 changes: 96 additions & 6 deletions cmd/web/templates/flight-record-map.partials.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,27 @@

// WebLogbook wlbFlightMap Namespace
wlbFlightMap = function () {
// popup overlay
const container = document.getElementById('popup');
const content = document.getElementById('popup-content');
const closer = document.getElementById('popup-closer');

const overlay = new ol.Overlay({
element: container,
autoPan: {
animation: {
duration: 250,
},
},
});

function closerOnClick() {
overlay.setPosition(undefined);
closer.blur();
return false;
}

closer.onclick = closerOnClick;

function midpoint(lat1, lon1, lat2, lon2) {

Expand Down Expand Up @@ -61,6 +82,7 @@ wlbFlightMap = function () {
source: new ol.source.OSM()
})
],
overlays: [overlay],
view: new ol.View({
center: ol.proj.fromLonLat([center.split("|")[1], center.split("|")[0]]),
zoom: 5
Expand Down Expand Up @@ -97,17 +119,38 @@ wlbFlightMap = function () {
points.push([dep["lon"],dep["lat"]])
points.push([arr["lon"],arr["lat"]])

var markers = [dep, arr];

var vectorMarker = new ol.source.Vector({});
var iconStyle = new ol.style.Style({
image: new ol.style.Icon(/** @type {module:ol/style/Icon~Options} */ ({
src: "/static/favicon.ico"
}))
})

for (var x = 0; x < points.length; x++) {
var iconStyle = [
new ol.style.Style({
image: new ol.style.Icon(/** @type {module:ol/style/Icon~Options} */ ({
src: "/static/favicon.ico"
}))
}),
new ol.style.Style({
text: new ol.style.Text({
text: markers[x]["name"],
offsetY: -12,
scale: 1.3,
fill: new ol.style.Fill({
color: '#333',
})
})
})
]

var featureMarker = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat(points[x])),
desc: "/static/favicon.ico"
desc: "/static/favicon.ico",
name: `${markers[x]["icao"]}/${markers[x]["iata"]}`,
civil_name: markers[x]["name"],
country: markers[x]["country"],
city: markers[x]["city"],
elevation: markers[x]["elevation"],
coordinates: `${markers[x]["lat"]}, ${markers[x]["lon"]}`,
});

featureMarker.setStyle(iconStyle);
Expand All @@ -127,6 +170,53 @@ wlbFlightMap = function () {
map.getView().fit(extent, {size:map.getSize(), maxZoom:16, padding: [20,20,20,20]});

document.getElementById("some_stats").innerText = "Distance: " + Math.floor(dist) + " km / " + Math.floor(dist/1.852) + " nm";

map.on('click', function (evt) {
const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
return feature;
});

// cliked somewhere on the map
if (!feature) {
closerOnClick();
return;
}

// clicked but not on the marker
if (feature.get("name") === undefined) {
closerOnClick();
return;
}

// show airport/marker info
const coordinates = feature.getGeometry().getCoordinates();
content.innerHTML =
'<strong>Airport:</strong> ' + feature.get('name') + '<br>' +
'<strong>Name:</strong> ' + feature.get('civil_name') + '<br>' +
'<strong>Country:</strong> ' + feature.get('country') + '<br>' +
'<strong>Elevation:</strong> ' + feature.get('elevation') + '<br>' +
'<strong>Lat/Lon:</strong> ' + feature.get('coordinates') + '<br>';
overlay.setPosition(coordinates);
});

map.on('pointermove', function (e) {
const pixel = map.getEventPixel(e.originalEvent);

var is_marker = false;
features = map.getFeaturesAtPixel(pixel);
for (var x = 0; x < features.length; x++) {
if (features[x].get("name") !== undefined) {
is_marker = true;
}
}

if(is_marker) {
const hit = map.hasFeatureAtPixel(pixel);
document.getElementById('map').style.cursor = hit ? 'pointer' : '';
} else {
document.getElementById('map').style.cursor = '';
}
});
}

return {
Expand Down
Loading

0 comments on commit 7496da0

Please sign in to comment.