Merge branch 'taxonomy'

This commit is contained in:
Dirk Hohndel 2015-07-02 07:03:03 -07:00
commit f554c2d16c
20 changed files with 1358 additions and 65 deletions

View file

@ -306,6 +306,7 @@ set(SUBSURFACE_CORE_LIB_SRCS
configuredivecomputer.cpp
configuredivecomputerthreads.cpp
divesitehelpers.cpp
taxonomy.c
checkcloudconnection.cpp
windowtitleupdate.cpp
divelogexportlogic.cpp

View file

@ -169,6 +169,19 @@ void copy_dive_site(struct dive_site *orig, struct dive_site *copy)
copy->notes = copy_string(orig->notes);
copy->description = copy_string(orig->description);
copy->uuid = orig->uuid;
copy->taxonomy.nr = orig->taxonomy.nr;
if (orig->taxonomy.category == NULL) {
free(copy->taxonomy.category);
copy->taxonomy.category = NULL;
} else {
if (copy->taxonomy.category == NULL)
copy->taxonomy.category = alloc_taxonomy();
for (int i = 0; i < NR_CATEGORIES; i++) {
free((void *)copy->taxonomy.category[i].value);
copy->taxonomy.category[i] = orig->taxonomy.category[i];
copy->taxonomy.category[i].value = copy_string(orig->taxonomy.category[i].value);
}
}
}
void clear_dive_site(struct dive_site *ds)
@ -182,4 +195,6 @@ void clear_dive_site(struct dive_site *ds)
ds->latitude.udeg = 0;
ds->longitude.udeg = 0;
ds->uuid = 0;
ds->taxonomy.nr = 0;
free_taxonomy(ds->taxonomy.category);
}

View file

@ -2,6 +2,7 @@
#define DIVESITE_H
#include "units.h"
#include "taxonomy.h"
#include <stdlib.h>
#ifdef __cplusplus
@ -17,6 +18,7 @@ struct dive_site
degrees_t latitude, longitude;
char *description;
char *notes;
struct taxonomy_data taxonomy;
};
struct dive_site_table {

View file

@ -41,54 +41,134 @@ void ReverseGeoLookupThread::run() {
QNetworkRequest request;
QNetworkAccessManager *rgl = new QNetworkAccessManager();
QEventLoop loop;
QString mapquestURL("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3");
QString geonamesURL("http://api.geonames.org/findNearbyPlaceNameJSON?language=%1&lat=%2&lng=%3&radius=50&username=dirkhh");
QString geonamesOceanURL("http://api.geonames.org/oceanJSON?language=%1&lat=%2&lng=%3&radius=50&username=dirkhh");
QString divelogsURL("https://www.divelogs.de/mapsearch_divespotnames.php?lat=%1&lng=%2&radius=50");
QTimer timer;
request.setRawHeader("Accept", "text/json");
request.setRawHeader("User-Agent", getUserAgent().toUtf8());
QEventLoop loop;
QString apiCall("http://open.mapquestapi.com/nominatim/v1/reverse.php?format=json&accept-language=%1&lat=%2&lon=%3");
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
Q_FOREACH (const GeoLookupInfo& info, geo_lookup_data ) {
request.setUrl(apiCall.arg(uiLanguage(NULL)).arg(info.lat.udeg / 1000000.0).arg(info.lon.udeg / 1000000.0));
struct dive_site *ds = get_dive_site_by_uuid(info.uuid);
// first check the findNearbyPlaces API from geonames - that should give us country, state, city
request.setUrl(geonamesURL.arg(uiLanguage(NULL)).arg(info.lat.udeg / 1000000.0).arg(info.lon.udeg / 1000000.0));
QNetworkReply *reply = rgl->get(request);
QTimer timer;
timer.setSingleShot(true);
QEventLoop loop;
connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
timer.start(500); // 30 secs. timeout
timer.start(5000); // 5 secs. timeout
loop.exec();
if(timer.isActive()) {
timer.stop();
if(reply->error() > 0)
if(reply->error() > 0) {
report_error("got error accessing geonames.org: %s", reply->errorString());
goto clear_reply;
}
int v = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (v < 200 || v >= 300)
goto clear_reply;
QByteArray fullReply = reply->readAll();
QJsonParseError errorObject;
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &errorObject);
if (errorObject.error != QJsonParseError::NoError)
QJsonDocument jsonDoc = QJsonDocument::fromJson(fullReply, &errorObject);
if (errorObject.error != QJsonParseError::NoError) {
report_error("error parsing geonames.org response: %s", errorObject.errorString());
goto clear_reply;
}
QJsonObject obj = jsonDoc.object();
QJsonObject address = obj.value("address").toObject();
struct dive_site *ds = get_dive_site_by_uuid(info.uuid);
ds->notes = add_to_string(ds->notes, "countrytag: %s", address.value("country").toString().toUtf8().data());
QVariant geoNamesObject = obj.value("geonames").toVariant();
QVariantList geoNames = geoNamesObject.toList();
if (geoNames.count() > 0) {
QVariantMap firstData = geoNames.at(0).toMap();
int ri = 0;
if (ds->taxonomy.category == NULL)
ds->taxonomy.category = alloc_taxonomy();
// get all the data - OCEAN is special, so start at COUNTRY
for (int j = COUNTRY; j < NR_CATEGORIES; j++) {
if (firstData[taxonomy_api_names[j]].isValid()) {
ds->taxonomy.category[ri].category = j;
ds->taxonomy.category[ri].origin = taxonomy::GEOCODED;
free((void*)ds->taxonomy.category[ri].value);
ds->taxonomy.category[ri].value = copy_string(qPrintable(firstData[taxonomy_api_names[j]].toString()));
ri++;
}
}
ds->taxonomy.nr = ri;
mark_divelist_changed(true);
} else {
report_error("geonames.org did not provide reverse lookup information");
qDebug() << "no reverse geo lookup; geonames returned\n" << fullReply;
}
} else {
report_error("timeout accessing geonames.org");
disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
reply->abort();
}
// next check the oceans API to figure out the body of water
request.setUrl(geonamesOceanURL.arg(uiLanguage(NULL)).arg(info.lat.udeg / 1000000.0).arg(info.lon.udeg / 1000000.0));
reply = rgl->get(request);
connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
timer.start(5000); // 5 secs. timeout
loop.exec();
if(timer.isActive()) {
timer.stop();
if(reply->error() > 0) {
report_error("got error accessing oceans API of geonames.org: %s", reply->errorString());
goto clear_reply;
}
int v = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (v < 200 || v >= 300)
goto clear_reply;
QByteArray fullReply = reply->readAll();
QJsonParseError errorObject;
QJsonDocument jsonDoc = QJsonDocument::fromJson(fullReply, &errorObject);
if (errorObject.error != QJsonParseError::NoError) {
report_error("error parsing geonames.org response: %s", errorObject.errorString());
goto clear_reply;
}
QJsonObject obj = jsonDoc.object();
QVariant oceanObject = obj.value("ocean").toVariant();
QVariantMap oceanName = oceanObject.toMap();
if (oceanName["name"].isValid()) {
if (ds->taxonomy.category == NULL)
ds->taxonomy.category = alloc_taxonomy();
ds->taxonomy.category[ds->taxonomy.nr].category = OCEAN;
ds->taxonomy.category[ds->taxonomy.nr].origin = taxonomy::GEOCODED;
ds->taxonomy.category[ds->taxonomy.nr].value = copy_string(qPrintable(oceanName["name"].toString()));
ds->taxonomy.nr++;
mark_divelist_changed(true);
}
} else {
report_error("timeout accessing geonames.org");
disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
reply->abort();
}
clear_reply:
clear_reply:
reply->deleteLater();
}
rgl->deleteLater();
}
void ReverseGeoLookupThread::lookup(dive_site *ds)
{
if (!ds)
return;
GeoLookupInfo info;
info.lat = ds->latitude;
info.lon = ds->longitude;
info.uuid = ds->uuid;
geo_lookup_data.clear();
geo_lookup_data.append(info);
run();
}
extern "C" void add_geo_information_for_lookup(degrees_t latitude, degrees_t longitude, uint32_t uuid) {
GeoLookupInfo info;
info.lat = latitude;

View file

@ -8,6 +8,7 @@ class ReverseGeoLookupThread : public QThread {
Q_OBJECT
public:
static ReverseGeoLookupThread *instance();
void lookup(struct dive_site *ds);
void run() Q_DECL_OVERRIDE;
private:

1010
icons/geocode.svg Normal file

File diff suppressed because it is too large Load diff

After

Width:  |  Height:  |  Size: 38 KiB

View file

@ -300,6 +300,22 @@ static void parse_site_gps(char *line, struct membuffer *str, void *_ds)
ds->longitude = parse_degrees(line, &line);
}
static void parse_site_geo(char *line, struct membuffer *str, void *_ds)
{
fprintf(stderr, "line |%s| str |%s|\n", line, mb_cstring(str));
struct dive_site *ds = _ds;
if (ds->taxonomy.category == NULL)
ds->taxonomy.category = alloc_taxonomy();
int nr = ds->taxonomy.nr;
if (nr < NR_CATEGORIES) {
struct taxonomy *t = &ds->taxonomy.category[nr];
t->value = strdup(mb_cstring(str));
sscanf(line, "cat %d origin %d \"", &t->category, &t->origin);
fprintf(stderr, "found category %d origin %d value |%s|\n", t->category, t->origin, t->value);
ds->taxonomy.nr++;
}
}
/* Parse key=val parts of samples and cylinders etc */
static char *parse_keyvalue_entry(void (*fn)(void *, const char *, const char *), void *fndata, char *line)
{
@ -906,7 +922,7 @@ static void dive_parser(char *line, struct membuffer *str, void *_dive)
struct keyword_action site_action[] = {
#undef D
#define D(x) { #x, parse_site_ ## x }
D(description), D(gps), D(name), D(notes)
D(description), D(geo), D(gps), D(name), D(notes)
};
static void site_parser(char *line, struct membuffer *str, void *_ds)

View file

@ -1431,6 +1431,8 @@ static void try_to_fill_dive_site(struct dive_site **ds_p, const char *name, cha
start_match("divesite", name, buf);
struct dive_site *ds = *ds_p;
if (ds->taxonomy.category == NULL)
ds->taxonomy.category = alloc_taxonomy();
if (MATCH("uuid", hex_value, &ds->uuid))
return;
@ -1442,6 +1444,15 @@ static void try_to_fill_dive_site(struct dive_site **ds_p, const char *name, cha
return;
if (MATCH("gps", gps_location, ds))
return;
if (MATCH("cat.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].category))
return;
if (MATCH("origin.geo", get_index, (int *)&ds->taxonomy.category[ds->taxonomy.nr].origin))
return;
if (MATCH("value.geo", utf8_string, &ds->taxonomy.category[ds->taxonomy.nr].value)) {
if (ds->taxonomy.nr < NR_CATEGORIES)
ds->taxonomy.nr++;
return;
}
nonmatch("divesite", name, buf);
}
@ -1517,14 +1528,17 @@ static void dive_site_end(void)
if (!cur_dive_site)
return;
if (cur_dive_site->uuid) {
uint32_t tmp = create_dive_site_with_gps(cur_dive_site->name, cur_dive_site->latitude, cur_dive_site->longitude);
struct dive_site *ds = get_dive_site_by_uuid(tmp);
ds->uuid = cur_dive_site->uuid;
ds->notes = cur_dive_site->notes;
ds->description = cur_dive_site->description;
struct dive_site *ds = alloc_dive_site();
if (cur_dive_site->taxonomy.nr == 0) {
free(cur_dive_site->taxonomy.category);
cur_dive_site->taxonomy.category = NULL;
}
copy_dive_site(cur_dive_site, ds);
if (verbose > 3)
printf("completed dive site uuid %x8 name {%s}\n", ds->uuid, ds->name);
}
free_taxonomy(cur_dive_site->taxonomy.category);
free(cur_dive_site);
cur_dive_site = NULL;
}

5
pref.h
View file

@ -6,6 +6,7 @@ extern "C" {
#endif
#include "units.h"
#include "taxonomy.h"
/* can't use 'bool' for the boolean values - different size in C and C++ */
typedef struct
@ -28,9 +29,7 @@ typedef struct {
bool enable_geocoding;
bool parse_dive_without_gps;
bool tag_existing_dives;
char *first_item;
char *second_item;
char *third_item;
enum taxonomy_category category[3];
} geocoding_prefs_t;
struct preferences {

View file

@ -116,6 +116,8 @@ GeoReferencingOptionsModel *GeoReferencingOptionsModel::instance() {
GeoReferencingOptionsModel::GeoReferencingOptionsModel(QObject *parent) : QStringListModel(parent)
{
QStringList list;
list << "Country" << "State" << "District" << "Town" << "Suburb" << "Body of Water" << "Site Name";
int i;
for (i = 0; i < NR_CATEGORIES; i++)
list << taxonomy_category_names[i];
setStringList(list);
}

View file

@ -77,6 +77,7 @@ MainTab::MainTab(QWidget *parent) : QTabWidget(parent),
ui.location->setCompleter(completer);
connect(ui.addDiveSite, SIGNAL(clicked()), this, SLOT(showDiveSiteSimpleEdit()));
connect(ui.geocodeButton, SIGNAL(clicked()), this, SLOT(reverseGeocode()));
QAction *action = new QAction(tr("Apply changes"), this);
connect(action, SIGNAL(triggered(bool)), this, SLOT(acceptChanges()));
@ -506,9 +507,26 @@ void MainTab::updateDiveInfo(bool clear)
if (!clear) {
struct dive_site *ds = get_dive_site_by_uuid(displayed_dive.dive_site_uuid);
ui.geocodeButton->setVisible(ds && dive_site_has_gps_location(ds));
if (ds) {
// construct the location tags
QString locationTag;
if (ds->taxonomy.nr) {
locationTag = "<small><small>(tags: ";
QString connector = "";
for (int i = 0; i < 3; i++) {
for (int j = 0; j < NR_CATEGORIES; j++) {
if (ds->taxonomy.category[j].category == prefs.geocoding.category[i]) {
locationTag += connector + QString(ds->taxonomy.category[j].value);
connector = " / ";
break;
}
}
}
locationTag += ")</small></small>";
}
ui.location->setText(ds->name);
ui.locationTags->setText(ds->description); // TODO: This should be three tags following davide's explanation.
ui.locationTags->setText(locationTag);
if (displayed_dive.dive_site_uuid)
copy_dive_site(get_dive_site_by_uuid(displayed_dive.dive_site_uuid), &displayed_dive_site);
} else {
@ -546,6 +564,7 @@ void MainTab::updateDiveInfo(bool clear)
ui.watertemp->setVisible(false);
// rename the remaining fields and fill data from selected trip
ui.LocationLabel->setText(tr("Trip location"));
ui.locationTags->clear();
ui.location->setText(currentTrip->location);
ui.NotesLabel->setText(tr("Trip notes"));
ui.notes->setText(currentTrip->notes);
@ -1538,3 +1557,10 @@ void MainTab::showAndTriggerEditSelective(struct dive_components what)
weightModel->changed = true;
}
}
void MainTab::reverseGeocode()
{
ReverseGeoLookupThread *geoLookup = ReverseGeoLookupThread::instance();
geoLookup->lookup(&displayed_dive_site);
MainWindow::instance()->information()->updateDiveInfo();
}

View file

@ -98,6 +98,7 @@ slots:
void disableGeoLookupEdition();
void setCurrentLocationIndex();
void showDiveSiteSimpleEdit();
void reverseGeocode();
private:
Ui::MainTab ui;
WeightModel *weightModel;

View file

@ -167,25 +167,38 @@
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="LocationLabel">
<property name="text">
<string>Location</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="locationTags">
<property name="text">
<string/>
</property>
</widget>
<layout class="QHBoxLayout" name="LocationLayout" stretch="0,1">
<item>
<widget class="QLabel" name="LocationLabel">
<property name="text">
<string>Location</string>
</property>
<property name="alignment">
<set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="locationTags">
<property name="text">
<string/>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
@ -206,6 +219,17 @@
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="geocodeButton">
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../subsurface.qrc">
<normaloff>:/geocode</normaloff>:/geocode</iconset>
</property>
</widget>
</item>
<item>
<widget class="QtWaitingSpinner" name="waitingSpinner" native="true"/>
</item>
@ -539,8 +563,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>743</height>
<width>100</width>
<height>30</height>
</rect>
</property>
<layout class="QGridLayout" name="equipmentTabScrollAreaLayout">
@ -634,8 +658,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>743</height>
<width>286</width>
<height>300</height>
</rect>
</property>
<layout class="QGridLayout" name="diveInfoScrollAreaLayout">
@ -975,8 +999,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>449</width>
<height>743</height>
<width>297</width>
<height>177</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">

View file

@ -241,9 +241,9 @@ void PreferencesDialog::setUiFromPrefs()
ui.enable_geocoding->setChecked( prefs.geocoding.enable_geocoding );
ui.parse_without_gps->setChecked(prefs.geocoding.parse_dive_without_gps);
ui.tag_existing_dives->setChecked(prefs.geocoding.tag_existing_dives);
ui.first_item->setCurrentText(prefs.geocoding.first_item);
ui.second_item->setCurrentText(prefs.geocoding.second_item);
ui.third_item->setCurrentText(prefs.geocoding.third_item);
ui.first_item->setCurrentIndex(prefs.geocoding.category[0]);
ui.second_item->setCurrentIndex(prefs.geocoding.category[1]);
ui.third_item->setCurrentIndex(prefs.geocoding.category[2]);
}
void PreferencesDialog::restorePrefs()
@ -288,6 +288,13 @@ void PreferencesDialog::rememberPrefs()
else \
prefs.field = default_prefs.field
#define GET_ENUM(name, type, field) \
v = s.value(QString(name)); \
if (v.isValid()) \
prefs.field = (enum type)v.toInt(); \
else \
prefs.field = default_prefs.field
#define GET_INT_DEF(name, field, defval) \
v = s.value(QString(name)); \
if (v.isValid()) \
@ -455,9 +462,9 @@ void PreferencesDialog::syncSettings()
s.setValue("enable_geocoding", ui.enable_geocoding->isChecked());
s.setValue("parse_dives_without_gps", ui.parse_without_gps->isChecked());
s.setValue("tag_existing_dives", ui.tag_existing_dives->isChecked());
s.setValue("first_item", ui.first_item->currentText());
s.setValue("second_item", ui.second_item->currentText());
s.setValue("third_item", ui.third_item->currentText());
s.setValue("cat0", ui.first_item->currentIndex());
s.setValue("cat1", ui.second_item->currentIndex());
s.setValue("cat2", ui.third_item->currentIndex());
s.endGroup();
loadSettings();
@ -603,9 +610,9 @@ void PreferencesDialog::loadSettings()
GET_BOOL("enable_geocoding", geocoding.enable_geocoding);
GET_BOOL("parse_dives_without_gps", geocoding.parse_dive_without_gps);
GET_BOOL("tag_existing_dives", geocoding.tag_existing_dives);
GET_TXT("first_item", geocoding.first_item);
GET_TXT("second_item", geocoding.second_item);
GET_TXT("third_item", geocoding.third_item);
GET_ENUM("cat0", taxonomy_category, geocoding.category[0]);
GET_ENUM("cat1", taxonomy_category, geocoding.category[1]);
GET_ENUM("cat2", taxonomy_category, geocoding.category[2]);
s.endGroup();
}

View file

@ -897,6 +897,14 @@ static void save_divesites(git_repository *repo, struct dir *tree)
show_utf8(&b, "description ", ds->description, "\n");
show_utf8(&b, "notes ", ds->notes, "\n");
show_gps(&b, ds->latitude, ds->longitude);
if (prefs.geocoding.enable_geocoding)
for (int j = 0; j < ds->taxonomy.nr; j++) {
struct taxonomy *t = &ds->taxonomy.category[j];
if (t->category != NONE) {
put_format(&b, "geo cat %d origin %d ", t->category, t->origin);
show_utf8(&b, "", t->value, "\n" );
}
}
blob_insert(repo, subdir, &b, mb_cstring(&site_file_name));
}
}

View file

@ -539,7 +539,20 @@ void save_dives_buffer(struct membuffer *b, const bool select_only)
}
show_utf8(b, ds->description, " description='", "'", 1);
show_utf8(b, ds->notes, " notes='", "'", 1);
put_format(b, "/>\n");
if (prefs.geocoding.enable_geocoding && ds->taxonomy.nr) {
put_format(b, ">\n");
for (int j = 0; j < ds->taxonomy.nr; j++) {
struct taxonomy *t = &ds->taxonomy.category[j];
if (t->category != NONE) {
put_format(b, "<geo cat='%d'", t->category);
put_format(b, " origin='%d'", t->origin);
show_utf8(b, t->value, " value='", "'/>\n", 1);
}
}
put_format(b, "</site>\n");
} else {
put_format(b, "/>\n");
}
}
put_format(b, "</divesites>\n<dives>\n");
for (trip = dive_trip_list; trip != NULL; trip = trip->next)

View file

@ -78,5 +78,6 @@
<file alias="filter-close">icons/process-stop.svg</file>
<file alias="edit">icons/edit-circled.svg</file>
<file alias="satellite">icons/Emblem-earth.svg</file>
<file alias="geocode">icons/geocode.svg</file>
</qresource>
</RCC>

View file

@ -74,9 +74,7 @@ struct preferences default_prefs = {
.enable_geocoding = false,
.parse_dive_without_gps = false,
.tag_existing_dives = false,
.first_item = NULL,
.second_item = NULL,
.third_item = NULL
.category = { 0 }
}
};

36
taxonomy.c Normal file
View file

@ -0,0 +1,36 @@
#include "taxonomy.h"
#include "gettext.h"
#include <stdlib.h>
char *taxonomy_category_names[NR_CATEGORIES] = {
QT_TRANSLATE_NOOP("getTextFromC", "None"),
QT_TRANSLATE_NOOP("getTextFromC", "Ocean"),
QT_TRANSLATE_NOOP("getTextFromC", "Country"),
QT_TRANSLATE_NOOP("getTextFromC", "State"),
QT_TRANSLATE_NOOP("getTextFromC", "County"),
QT_TRANSLATE_NOOP("getTextFromC", "City")
};
// these are the names for geoname.org
char *taxonomy_api_names[NR_CATEGORIES] = {
"none",
"name",
"countryName",
"adminName1",
"adminName2",
"toponymName"
};
struct taxonomy *alloc_taxonomy()
{
return calloc(NR_CATEGORIES, sizeof(struct taxonomy));
}
void free_taxonomy(struct taxonomy *t)
{
if (t) {
for (int i = 0; i < NR_CATEGORIES; i++)
free((void *)t[i].value);
free(t);
}
}

39
taxonomy.h Normal file
View file

@ -0,0 +1,39 @@
#ifndef TAXONOMY_H
#define TAXONOMY_H
#ifdef __cplusplus
extern "C" {
#endif
enum taxonomy_category {
NONE,
OCEAN,
COUNTRY,
ADMIN_L1,
ADMIN_L2,
LOCALNAME,
NR_CATEGORIES
};
extern char *taxonomy_category_names[NR_CATEGORIES];
extern char *taxonomy_api_names[NR_CATEGORIES];
struct taxonomy {
int category; /* the category for this tag: ocean, country, admin_l1, admin_l2, localname, etc */
const char *value; /* the value returned, parsed, or manually entered for that category */
enum { GEOCODED, PARSED, MANUAL } origin;
};
/* the data block contains 3 taxonomy structures - unused ones have a tag value of NONE */
struct taxonomy_data {
int nr;
struct taxonomy *category;
};
struct taxonomy *alloc_taxonomy();
void free_taxonomy(struct taxonomy *t);
#ifdef __cplusplus
}
#endif
#endif // TAXONOMY_H