summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am8
-rw-r--r--src/db.c88
-rw-r--r--src/db.h9
-rw-r--r--src/import_tiger.c168
-rw-r--r--src/main.c4
-rw-r--r--src/main.h2
-rw-r--r--src/mainwindow.c202
-rw-r--r--src/map.c1138
-rw-r--r--src/map.h77
-rw-r--r--src/map_draw_cairo.c54
-rw-r--r--src/map_draw_cairo.h2
-rw-r--r--src/map_draw_gdk.c110
-rw-r--r--src/map_draw_gdk.h3
-rw-r--r--src/map_style.c15
-rw-r--r--src/map_tile.c4
-rw-r--r--src/map_tile.h1
-rw-r--r--src/map_tilemanager.c404
-rw-r--r--src/map_tilemanager.h45
-rw-r--r--src/road.c4
-rw-r--r--src/scenemanager.c17
-rw-r--r--src/scenemanager.h1
-rw-r--r--src/search_road.c28
22 files changed, 1165 insertions, 1219 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index 75e7953..aa9c0a9 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -25,11 +25,13 @@ roadster_SOURCES = \
mainwindow.c\
gotowindow.c\
map.c\
- map_tile.c\
- map_history.c\
- map_style.c\
map_draw_cairo.c\
map_draw_gdk.c\
+ map_history.c\
+ map_hittest.c\
+ map_math.c\
+ map_style.c\
+ map_tilemanager.c\
import.c\
import_tiger.c\
importwindow.c\
diff --git a/src/db.c b/src/db.c
index 034392b..70448f6 100644
--- a/src/db.c
+++ b/src/db.c
@@ -256,11 +256,6 @@ static guint db_count_table_rows(const gchar* pszTable)
return uRows;
}
-gboolean db_is_empty()
-{
- return (db_count_table_rows(DB_ROADS_TABLENAME) == 0);
-}
-
/******************************************************
** data inserting
******************************************************/
@@ -285,9 +280,9 @@ static gboolean db_insert(const gchar* pszSQL, gint* pnReturnRowsInserted)
return FALSE;
}
-gboolean db_insert_road(gint nRoadNameID, gint nLayerType, gint nAddressLeftStart, gint nAddressLeftEnd, gint nAddressRightStart, gint nAddressRightEnd, gint nCityLeftID, gint nCityRightID, const gchar* pszZIPCodeLeft, const gchar* pszZIPCodeRight, GPtrArray* pPointsArray, gint* pReturnID)
+gboolean db_insert_road(gint nLOD, gint nRoadNameID, gint nLayerType, gint nAddressLeftStart, gint nAddressLeftEnd, gint nAddressRightStart, gint nAddressRightEnd, gint nCityLeftID, gint nCityRightID, const gchar* pszZIPCodeLeft, const gchar* pszZIPCodeRight, GPtrArray* pPointsArray, gint* pReturnID)
{
- g_assert(pReturnID != NULL);
+// g_assert(pReturnID != NULL);
if(!db_is_connected()) return FALSE;
if(pPointsArray->len == 0) return TRUE; // skip 0-length
@@ -307,23 +302,32 @@ gboolean db_insert_road(gint nRoadNameID, gint nLayerType, gint nAddressLeftStar
nCount++;
}
- gchar azQuery[MAX_SQLBUFFER_LEN];
- g_snprintf(azQuery, MAX_SQLBUFFER_LEN,
- "INSERT INTO %s SET RoadNameID=%d, TypeID=%d, Coordinates=GeometryFromText('LINESTRING(%s)')"
- ", AddressLeftStart=%d, AddressLeftEnd=%d, AddressRightStart=%d, AddressRightEnd=%d"
- ", CityLeftID=%d, CityRightID=%d"
- ", ZIPCodeLeft='%s', ZIPCodeRight='%s'",
- DB_ROADS_TABLENAME, nRoadNameID, nLayerType, azCoordinateList,
- nAddressLeftStart, nAddressLeftEnd, nAddressRightStart, nAddressRightEnd,
- nCityLeftID, nCityRightID,
- pszZIPCodeLeft, pszZIPCodeRight);
-
- if(MYSQL_RESULT_SUCCESS != mysql_query(g_pDB->pMySQLConnection, azQuery)) {
- g_warning("db_insert_road failed: %s (SQL: %s)\n", mysql_error(g_pDB->pMySQLConnection), azQuery);
- return FALSE;
+ gchar* pszQuery;
+
+ if(nLOD == 0) {
+ pszQuery = g_strdup_printf(
+ "INSERT INTO %s%d SET RoadNameID=%d, TypeID=%d, Coordinates=GeometryFromText('LINESTRING(%s)')"
+ ", AddressLeftStart=%d, AddressLeftEnd=%d, AddressRightStart=%d, AddressRightEnd=%d"
+ ", CityLeftID=%d, CityRightID=%d"
+ ", ZIPCodeLeft='%s', ZIPCodeRight='%s'",
+ DB_ROADS_TABLENAME, nLOD, nRoadNameID, nLayerType, azCoordinateList,
+ nAddressLeftStart, nAddressLeftEnd, nAddressRightStart, nAddressRightEnd,
+ nCityLeftID, nCityRightID,
+ pszZIPCodeLeft, pszZIPCodeRight);
+ }
+ else {
+ pszQuery = g_strdup_printf(
+ "INSERT INTO %s%d SET RoadNameID=%d, TypeID=%d, Coordinates=GeometryFromText('LINESTRING(%s)')",
+ DB_ROADS_TABLENAME, nLOD, nRoadNameID, nLayerType, azCoordinateList);
}
+
+ mysql_query(g_pDB->pMySQLConnection, pszQuery);
+ g_free(pszQuery);
+
// return the new ID
- *pReturnID = mysql_insert_id(g_pDB->pMySQLConnection);
+ if(pReturnID != NULL) {
+ *pReturnID = mysql_insert_id(g_pDB->pMySQLConnection);
+ }
return TRUE;
}
@@ -571,32 +575,43 @@ void db_create_tables()
// db_query("ALTER TABLE RoadName ADD INDEX (NameSoundex);", NULL);
// Road
- db_query("CREATE TABLE IF NOT EXISTS Road("
- " ID INT4 UNSIGNED NOT NULL AUTO_INCREMENT," // XXX: can we get away with INT3 ?
+ db_query(
+ "CREATE TABLE IF NOT EXISTS Road0("
+// " ID INT4 UNSIGNED NOT NULL AUTO_INCREMENT," // XXX: can we get away with INT3 ?
" TypeID INT1 UNSIGNED NOT NULL,"
-
" RoadNameID INT3 UNSIGNED NOT NULL," // NOTE: 3 bytes
-
" AddressLeftStart INT2 UNSIGNED NOT NULL,"
" AddressLeftEnd INT2 UNSIGNED NOT NULL,"
" AddressRightStart INT2 UNSIGNED NOT NULL,"
" AddressRightEnd INT2 UNSIGNED NOT NULL,"
-
" CityLeftID INT3 UNSIGNED NOT NULL," // NOTE: 3 bytes
" CityRightID INT3 UNSIGNED NOT NULL," // NOTE: 3 bytes
-
" ZIPCodeLeft CHAR(6) NOT NULL,"
" ZIPCodeRight CHAR(6) NOT NULL,"
-
" Coordinates point NOT NULL,"
// lots of indexes:
- " PRIMARY KEY (ID)," // XXX: we'll probably want to keep a unique ID, but we don't use this for anything yet.
+// " PRIMARY KEY (ID)," // XXX: we'll probably want to keep a unique ID, but we don't use this for anything yet.
" INDEX(RoadNameID)," // to get roads when we've matched a RoadName
" SPATIAL KEY (Coordinates));", NULL);
+ db_query(
+ "CREATE TABLE IF NOT EXISTS Road1("
+ " TypeID INT1 UNSIGNED NOT NULL,"
+ " RoadNameID INT3 UNSIGNED NOT NULL," // NOTE: 3 bytes
+ " Coordinates point NOT NULL,"
+ " SPATIAL KEY (Coordinates));", NULL);
+
+ db_query(
+ "CREATE TABLE IF NOT EXISTS Road2("
+ " TypeID INT1 UNSIGNED NOT NULL,"
+ " RoadNameID INT3 UNSIGNED NOT NULL," // NOTE: 3 bytes
+ " Coordinates point NOT NULL,"
+ " SPATIAL KEY (Coordinates));", NULL);
+
// RoadName
- db_query("CREATE TABLE IF NOT EXISTS RoadName("
+ db_query(
+ "CREATE TABLE IF NOT EXISTS RoadName("
" ID INT3 UNSIGNED NOT NULL auto_increment," // NOTE: 3 bytes
" Name VARCHAR(30) NOT NULL,"
" NameSoundex CHAR(10) NOT NULL," // see soundex() function
@@ -607,8 +622,8 @@ void db_create_tables()
,NULL);
// City
- db_query("CREATE TABLE IF NOT EXISTS City("
- // a unique ID for the value
+ db_query(
+ "CREATE TABLE IF NOT EXISTS City("
" ID INT3 UNSIGNED NOT NULL AUTO_INCREMENT," // NOTE: 3 bytes
" StateID INT2 UNSIGNED NOT NULL," // NOTE: 2 bytes
" Name CHAR(60) NOT NULL," // are city names ever 60 chars anyway?? TIGER think so
@@ -618,8 +633,8 @@ void db_create_tables()
,NULL);
// State
- db_query("CREATE TABLE IF NOT EXISTS State("
- // a unique ID for the value
+ db_query(
+ "CREATE TABLE IF NOT EXISTS State("
" ID INT2 UNSIGNED NOT NULL AUTO_INCREMENT," // NOTE: 2 bytes (enough to go global..?)
" Name CHAR(40) NOT NULL,"
" Code CHAR(3) NOT NULL," // eg. "MA"
@@ -629,7 +644,8 @@ void db_create_tables()
,NULL);
// Location
- db_query("CREATE TABLE IF NOT EXISTS Location("
+ db_query(
+ "CREATE TABLE IF NOT EXISTS Location("
" ID INT4 UNSIGNED NOT NULL AUTO_INCREMENT,"
" LocationSetID INT3 NOT NULL," // NOTE: 3 bytes
" Coordinates point NOT NULL,"
diff --git a/src/db.h b/src/db.h
index acdcae7..d34ea61 100644
--- a/src/db.h
+++ b/src/db.h
@@ -52,8 +52,6 @@ gboolean db_connect(const gchar* pzHost, const gchar* pzUserName, const gchar* p
const gchar* db_get_connection_info(void);
// utility
-gboolean db_is_empty(void);
-
gboolean db_insert_roadname(const gchar* pszName, gint nSuffixID, gint* pnReturnID);
//~ gboolean db_create_points_db(const gchar* name);
@@ -86,12 +84,7 @@ void db_enable_keys(void);
void db_disable_keys(void);
gboolean db_insert_city(const gchar* pszName, gint nStateID, gint* pnReturnCityID);
-gboolean db_insert_road(gint nRoadNameID, gint nLayerType,
- gint nAddressLeftStart, gint nAddressLeftEnd,
- gint nAddressRightStart, gint nAddressRightEnd,
- gint nCityLeftID, gint nCityRightID,
- const gchar* pszZIPCodeLeft, const gchar* pszZIPCodeRight,
- GPtrArray* pPointsArray, gint* pReturnID);
+gboolean db_insert_road(gint nLOD, gint nRoadNameID, gint nLayerType, gint nAddressLeftStart, gint nAddressLeftEnd, gint nAddressRightStart, gint nAddressRightEnd, gint nCityLeftID, gint nCityRightID, const gchar* pszZIPCodeLeft, const gchar* pszZIPCodeRight, GPtrArray* pPointsArray, gint* pReturnID);
gboolean db_city_get_id(const gchar* pszName, gint nStateID, gint* pnReturnID);
gboolean db_state_get_id(const gchar* pszName, gint* pnReturnID);
diff --git a/src/import_tiger.c b/src/import_tiger.c
index b8c823b..71887b3 100644
--- a/src/import_tiger.c
+++ b/src/import_tiger.c
@@ -155,6 +155,96 @@ typedef struct tiger_record_rtc
gint nCityID; // a database ID, stored here after it is inserted
} tiger_record_rtc_t;
+// #define MAP_OBJECT_TYPE_NONE (0)
+// #define MAP_OBJECT_TYPE_MINORROAD (1)
+// #define MAP_OBJECT_TYPE_MAJORROAD (2)
+// #define MAP_OBJECT_TYPE_MINORHIGHWAY (3)
+// #define MAP_OBJECT_TYPE_MINORHIGHWAY_RAMP (4)
+// #define MAP_OBJECT_TYPE_MAJORHIGHWAY (5) // Unused
+// #define MAP_OBJECT_TYPE_MAJORHIGHWAY_RAMP (6) // Unused
+// #define MAP_OBJECT_TYPE_RAILROAD (7)
+// #define MAP_OBJECT_TYPE_PARK (8)
+// #define MAP_OBJECT_TYPE_RIVER (9)
+// #define MAP_OBJECT_TYPE_LAKE (10)
+// #define MAP_OBJECT_TYPE_MISC_AREA (11)
+// #define MAP_OBJECT_TYPE_URBAN_AREA (12)
+
+gdouble g_afPolygonMinSizeAtLODs[MAP_NUM_LEVELS_OF_DETAIL] = {0, 0.001, 0.01}; // in world degrees
+
+gint g_aaObjectTypeDetailAtLODs[MAP_NUM_OBJECT_TYPES][MAP_NUM_LEVELS_OF_DETAIL] = {
+ {0,0,0,0},
+ {1,0,0,0}, // minor rd
+ {1,6,0,0}, // major rd
+ {1,6,12,0}, // hw
+ {1,3,0,0}, // hw ramp
+ {0,0,0,0}, // (unused)
+ {0,0,0,0}, // (unused)
+ {1,8,0,0}, // rail
+ {1,1,1,1}, // park
+ {2,16,0,0}, // river
+ {1,1,1,1}, // lake
+ {1,1,1,1}, // misc area
+ {1,1,1,1}, // urban area
+};
+
+gboolean object_type_exists_at_lod(gint nRecordType, gint nLOD)
+{
+ if(nRecordType < 0) {
+ g_warning("nRecordType = %d\n", nRecordType);
+ }
+ g_assert(nRecordType >= 0);
+ g_assert(nRecordType < MAP_NUM_OBJECT_TYPES);
+ g_assert(nLOD >= 0);
+ g_assert(nLOD <= 3);
+ return (g_aaObjectTypeDetailAtLODs[nRecordType][nLOD] > 0);
+}
+
+gint object_type_detail_at_lod(gint nRecordType, gint nLOD)
+{
+ g_assert(nRecordType >= 0);
+ g_assert(nRecordType < MAP_NUM_OBJECT_TYPES);
+ g_assert(nLOD >= 0);
+ g_assert(nLOD <= 3);
+ return (g_aaObjectTypeDetailAtLODs[nRecordType][nLOD]);
+}
+
+void reduce_object_detail_for_lod(gint nRecordType, gint nLOD, GPtrArray* pSourceArray, GPtrArray* pDestArray)
+{
+ g_assert(pSourceArray);
+ g_assert(pDestArray);
+
+ if(!object_type_exists_at_lod(nRecordType, nLOD)) return;
+
+ gint nDetail = object_type_detail_at_lod(nRecordType, nLOD);
+ g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, 0));
+
+ // our super-hacky algorithm just steps N points at a time
+ gint i;
+ for(i = nDetail ; i < (pSourceArray->len-1) ; i+=nDetail) {
+ g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, i));
+ }
+ g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, pSourceArray->len-1));
+}
+
+void util_bounding_box_of_points_array(GPtrArray* pPointsArray, maprect_t* pReturnRect)
+{
+ pReturnRect->A.fLatitude = MAX_LATITUDE; // init to worst possible values
+ pReturnRect->A.fLongitude = MAX_LONGITUDE;
+
+ pReturnRect->B.fLatitude = MIN_LATITUDE;
+ pReturnRect->B.fLongitude = MIN_LONGITUDE;
+
+ gint i;
+ for(i=0 ; i<pPointsArray->len ; i++) {
+ mappoint_t* pPoint = g_ptr_array_index(pPointsArray, i);
+
+ pReturnRect->A.fLatitude = min(pReturnRect->A.fLatitude, pPoint->fLatitude);
+ pReturnRect->A.fLongitude = min(pReturnRect->A.fLongitude, pPoint->fLongitude);
+
+ pReturnRect->B.fLatitude = max(pReturnRect->B.fLatitude, pPoint->fLatitude);
+ pReturnRect->B.fLongitude = max(pReturnRect->B.fLongitude, pPoint->fLongitude);
+ }
+}
static gboolean import_tiger_read_lat(gint8* pBuffer, gdouble* pValue)
{
@@ -254,6 +344,8 @@ static gboolean import_tiger_read_string(char* pBuffer, gint nLen, char* pValue)
return TRUE;
}
+// NOTE: This function can return MAP_OBJECT_TYPE_NONE. Lines of this type shouldn't be saved, but they
+// might be used for polygons, so we have to keep them in memory.
static gboolean import_tiger_read_layer_type(gint8* pBuffer, gint* pValue)
{
//g_print("%c%c%c\n", *(pBuffer), *(pBuffer+1), *(pBuffer+2));
@@ -282,7 +374,6 @@ static gboolean import_tiger_read_layer_type(gint8* pBuffer, gint* pValue)
}
else if(chCode == '5') { // dirt roads
//*pValue = MAP_OBJECT_TYPE_TRAIL;
- return FALSE;
}
else if(chCode == '6') {
if(chSubCode == '1') {
@@ -294,7 +385,6 @@ static gboolean import_tiger_read_layer_type(gint8* pBuffer, gint* pValue)
}
else if(chSubCode == '5') {
//*pValue = MAP_OBJECT_TYPE_FERRY_ROUTE; // where a boat carrying cars goes
- return FALSE;
}
else if(chSubCode == '7') {
g_print("found code A67: toll booth!\n");
@@ -587,8 +677,8 @@ static gboolean import_tiger_parse_table_7(gint8* pBuffer, gint nLength, GHashTa
// 22-24 is a CFCC (
gint nRecordType;
-
import_tiger_read_layer_type(&pLine[22-1], &nRecordType);
+
pRecord = g_new0(tiger_record_rt7_t, 1);
pRecord->nRecordType = nRecordType;
@@ -832,16 +922,26 @@ static void callback_save_rt1_chains(gpointer key, gpointer value, gpointer user
db_insert_roadname(pRecordRT1->achName, pRecordRT1->nRoadNameSuffixID, &nRoadNameID);
}
- gint nRoadID;
- db_insert_road(nRoadNameID,
- pRecordRT1->nRecordType,
- pRecordRT1->nAddressLeftStart,
- pRecordRT1->nAddressLeftEnd,
- pRecordRT1->nAddressRightStart,
- pRecordRT1->nAddressRightEnd,
- nCityLeftID, nCityRightID,
- azZIPCodeLeft, azZIPCodeRight,
- pTempPointsArray, &nRoadID);
+ gint nLOD = MAP_LEVEL_OF_DETAIL_BEST;
+ db_insert_road(nLOD, nRoadNameID, pRecordRT1->nRecordType,
+ pRecordRT1->nAddressLeftStart, pRecordRT1->nAddressLeftEnd,
+ pRecordRT1->nAddressRightStart, pRecordRT1->nAddressRightEnd,
+ nCityLeftID, nCityRightID,
+ azZIPCodeLeft, azZIPCodeRight,
+ pTempPointsArray, NULL);
+
+ for(nLOD = MAP_LEVEL_OF_DETAIL_BEST+1 ; nLOD <= MAP_LEVEL_OF_DETAIL_WORST ; nLOD++) {
+ GPtrArray* pReducedPointsArray = g_ptr_array_new();
+
+ reduce_object_detail_for_lod(pRecordRT1->nRecordType, nLOD, pTempPointsArray, pReducedPointsArray);
+ if(pReducedPointsArray->len > 0) {
+ g_assert(pReducedPointsArray->len >= 2);
+
+ db_insert_road(nLOD, nRoadNameID, pRecordRT1->nRecordType, 0, 0, 0, 0, 0, 0, NULL, NULL,
+ pReducedPointsArray, NULL);
+ }
+ g_ptr_array_free(pReducedPointsArray, TRUE);
+ }
}
g_ptr_array_free(pTempPointsArray, TRUE);
}
@@ -1012,12 +1112,6 @@ static void callback_save_rti_polygons(gpointer key, gpointer value, gpointer us
g_print("Found a polygon that doesn't loop %s\n", pRecordRT7->achName);
}
- // XXX: looking up a city for a polygon? unimplemented.
- gint nCityLeftID = 0;
- gchar* pszZIPCodeLeft = "";
- gint nCityRightID = 0;
- gchar* pszZIPCodeRight = "";
-
// insert record
if(pRecordRT7->nRecordType != MAP_OBJECT_TYPE_NONE) {
gint nRoadNameID = 0;
@@ -1026,14 +1120,34 @@ static void callback_save_rti_polygons(gpointer key, gpointer value, gpointer us
db_insert_roadname(pRecordRT7->achName, 0, &nRoadNameID);
}
- gint nRoadID;
- db_insert_road(
- nRoadNameID,
- pRecordRT7->nRecordType,
- 0,0,0,0,
- nCityLeftID, nCityRightID,
- pszZIPCodeLeft, pszZIPCodeRight,
- pTempPointsArray, &nRoadID);
+ // Write LOD 0
+ gint nLOD = MAP_LEVEL_OF_DETAIL_BEST;
+ db_insert_road(nLOD, nRoadNameID, pRecordRT7->nRecordType, 0, 0, 0, 0, 0, 0, "", "",
+ pTempPointsArray, NULL);
+
+ // Write higher LODs
+ maprect_t rc;
+ util_bounding_box_of_points_array(pTempPointsArray, &rc);
+ gdouble fWidth = rc.B.fLongitude - rc.A.fLongitude;
+ gdouble fHeight = rc.B.fLatitude - rc.A.fLatitude;
+
+ for(nLOD = MAP_LEVEL_OF_DETAIL_BEST+1 ; nLOD <= MAP_LEVEL_OF_DETAIL_WORST ; nLOD++) {
+ if((fWidth < g_afPolygonMinSizeAtLODs[nLOD]) || (fHeight < g_afPolygonMinSizeAtLODs[nLOD])) {
+ g_print("object exluded at LOD %d\n", nLOD);
+ break; // not visible (nor at higher LODs, so break instead of continue)
+ }
+
+ GPtrArray* pReducedPointsArray = g_ptr_array_new();
+
+ reduce_object_detail_for_lod(pRecordRT7->nRecordType, nLOD, pTempPointsArray, pReducedPointsArray);
+ if(pReducedPointsArray->len > 0) {
+ g_assert(pReducedPointsArray->len >= 2);
+
+ db_insert_road(nLOD, nRoadNameID, pRecordRT7->nRecordType, 0, 0, 0, 0, 0, 0, NULL, NULL,
+ pReducedPointsArray, NULL);
+ }
+ g_ptr_array_free(pReducedPointsArray, TRUE);
+ }
}
}
diff --git a/src/main.c b/src/main.c
index f887d01..3321847 100644
--- a/src/main.c
+++ b/src/main.c
@@ -31,7 +31,6 @@
#include "db.h"
#include "map.h"
#include "gpsclient.h"
-#include "scenemanager.h"
#include "locationset.h"
#include "location.h"
#include "search.h"
@@ -142,9 +141,6 @@ gboolean main_init(void)
g_print("initializing map\n");
map_init();
- g_print("initializing scenemanager\n");
- scenemanager_init();
-
g_print("initializing gpsclient\n");
gpsclient_init();
diff --git a/src/main.h b/src/main.h
index 0e96270..15c19f8 100644
--- a/src/main.h
+++ b/src/main.h
@@ -24,6 +24,8 @@
#ifndef _MAIN_H_
#define _MAIN_H_
+//#define G_DISABLE_ASSERT
+
#include <gtk/gtk.h>
#define USE_GNOME_VFS // comment this out to get a faster single-threaded compile (can't import, though)
diff --git a/src/mainwindow.c b/src/mainwindow.c
index 8a75000..51e1fb8 100644
--- a/src/mainwindow.c
+++ b/src/mainwindow.c
@@ -27,7 +27,7 @@
#include <gtk/gtk.h>
#include <gtk/gtksignal.h>
-//#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
//#include <gdk/gdkx.h>
//#include <cairo.h>
//#include <cairo-xlib.h>
@@ -39,6 +39,7 @@
#include "gotowindow.h"
#include "db.h"
#include "map.h"
+#include "map_hittest.h"
#include "map_style.h"
#include "importwindow.h"
#include "locationset.h"
@@ -89,6 +90,8 @@
#define LOCATIONSETLIST_COLUMN_COUNT (3)
#define LOCATIONSETLIST_COLUMN_PIXBUF (4)
+#define ZOOM_TOOL_THRESHOLD (5) // in pixels. a box less than AxA will be ignored
+
// Limits
#define MAX_SEARCH_TEXT_LENGTH (100)
#define SPEED_LABEL_FORMAT ("<span font_desc='32'>%.0f</span>")
@@ -107,20 +110,20 @@
#define MAX_DISTANCE_FOR_AUTO_SLIDE_IN_PIXELS (3500.0) // when selecting search results, we slide to them instead of jumping if they are within this distance
// Types
-typedef struct {
- GdkCursorType CursorType;
- GdkCursor* pGdkCursor;
-} cursor_t;
+// typedef struct {
+// GdkCursorType CursorType;
+// GdkCursor* pGdkCursor;
+// } cursor_t;
-typedef struct {
- char* szName;
- cursor_t Cursor;
-} toolsettings_t;
+// typedef struct {
+// char* szName;
+// cursor_t Cursor;
+// } toolsettings_t;
-typedef enum {
- kToolPointer = 0,
- kToolZoom = 1,
-} EToolType;
+// typedef enum {
+// MOUSE_TOOL_POINTER = 0,
+// MOUSE_TOOL_ZOOM = 1,
+// } EMouseToolType;
// Prototypes
static void mainwindow_setup_selected_tool(void);
@@ -169,8 +172,8 @@ struct {
// Toolbar
GtkHBox* pToolbar;
-// GtkToolButton* pPointerToolButton;
-// GtkToolButton* pZoomToolButton;
+ GtkRadioButton* pPointerToolRadioButton;
+ GtkRadioButton* pZoomToolRadioButton;
GtkHScale* pZoomScale;
GtkEntry* pSearchBox;
GtkImage* pStatusbarGPSIcon;
@@ -214,7 +217,7 @@ struct {
tooltip_t* pTooltip;
map_t* pMap;
- EToolType eSelectedTool;
+// EToolType eSelectedTool;
gboolean bScrolling;
EDirection eScrollDirection;
@@ -236,6 +239,10 @@ struct {
mappoint_t ptSlideEndLocation;
animator_t* pAnimator;
+ // Zoom Tool
+ gboolean bDrawingZoomRect;
+ screenrect_t rcZoomRect;
+
// History (forward / back)
maphistory_t* pMapHistory;
GtkButton* pForwardButton;
@@ -252,17 +259,17 @@ struct {
// XXX: Use GDK_HAND1 for the map
// Data
-toolsettings_t g_Tools[] = {
- {"Pointer Tool", {GDK_LEFT_PTR, NULL}},
- {"Zoom Tool", {GDK_CIRCLE, NULL}},
-};
-void cursor_init()
-{
- int i;
- for(i=0 ; i<G_N_ELEMENTS(g_Tools) ; i++) {
- g_Tools[i].Cursor.pGdkCursor = gdk_cursor_new(g_Tools[i].Cursor.CursorType);
- }
-}
+// toolsettings_t g_Tools[] = {
+// {"Pointer Tool", {GDK_LEFT_PTR, NULL}},
+// {"Zoom Tool", {GDK_CIRCLE, NULL}},
+// };
+// void cursor_init()
+// {
+// int i;
+// for(i=0 ; i<G_N_ELEMENTS(g_Tools) ; i++) {
+// g_Tools[i].Cursor.pGdkCursor = gdk_cursor_new(g_Tools[i].Cursor.CursorType);
+// }
+// }
static void util_set_image_to_stock(GtkImage* pImage, gchar* pszStockIconID, GtkIconSize nSize)
{
@@ -294,6 +301,11 @@ void* mainwindow_set_busy(void)
return pCursor;
}
+void mainwindow_draw_xor_rect(screenrect_t* pRect)
+{
+ map_draw_gdk_xor_rect(g_MainWindow.pMap, GTK_WIDGET(g_MainWindow.pDrawingArea)->window, pRect);
+}
+
void mainwindow_set_not_busy(void** ppCursor)
{
gdk_window_set_cursor(GTK_WIDGET(g_MainWindow.pWindow)->window, NULL);
@@ -365,6 +377,10 @@ void mainwindow_init(GladeXML* pGladeXML)
GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pSidebarNotebook, GTK_NOTEBOOK, "sidebarnotebook");
GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pContentBox, GTK_VBOX, "mainwindowcontentsbox");
+// g_object_set(G_OBJECT(g_MainWindow.pSidebarNotebook), "show-border", FALSE, NULL);
+ g_object_set(G_OBJECT(g_MainWindow.pSidebarNotebook), "tab-border", 1, NULL);
+// g_object_set(G_OBJECT(g_MainWindow.pSidebarNotebook), "tab-hborder", 0, NULL);
+
// View menu
GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pViewSidebarMenuItem, GTK_CHECK_MENU_ITEM, "viewsidebarmenuitem");
GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pViewFullscreenMenuItem, GTK_CHECK_MENU_ITEM, "viewfullscreenmenuitem");
@@ -384,8 +400,8 @@ void mainwindow_init(GladeXML* pGladeXML)
GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pMapPopupMenu, GTK_MENU, "mappopupmenu");
// Tools
- //GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pPointerToolButton, GTK_TOOL_BUTTON, "pointertoolbutton");
- //GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pZoomToolButton, GTK_TOOL_BUTTON, "zoomtoolbutton");
+ GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pPointerToolRadioButton, GTK_RADIO_BUTTON, "pointertoolradiobutton");
+ GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.pZoomToolRadioButton, GTK_RADIO_BUTTON, "zoomtoolradiobutton");
// GPS Widgets
GLADE_LINK_WIDGET(pGladeXML, g_MainWindow.GPS.pShowPositionCheckButton, GTK_CHECK_BUTTON, "gpsshowpositioncheckbutton");
@@ -446,8 +462,7 @@ void mainwindow_init(GladeXML* pGladeXML)
map_new(&g_MainWindow.pMap, GTK_WIDGET(g_MainWindow.pDrawingArea));
map_style_load(g_MainWindow.pMap, MAP_STYLE_FILENAME);
-
- cursor_init();
+// cursor_init();
mainwindow_configure_locationset_list();
mainwindow_refresh_locationset_list();
@@ -737,6 +752,13 @@ void mainwindow_update_zoom_buttons()
gtk_widget_set_sensitive(GTK_WIDGET(g_MainWindow.pZoomInMenuItem), map_can_zoom_in(g_MainWindow.pMap));
gtk_widget_set_sensitive(GTK_WIDGET(g_MainWindow.pZoomOutButton), map_can_zoom_out(g_MainWindow.pMap));
gtk_widget_set_sensitive(GTK_WIDGET(g_MainWindow.pZoomOutMenuItem), map_can_zoom_out(g_MainWindow.pMap));
+
+ // set zoomlevel scale but prevent it from calling handler (mainwindow_on_zoomscale_value_changed)
+ g_signal_handlers_block_by_func(g_MainWindow.pZoomScale, mainwindow_on_zoomscale_value_changed, NULL);
+ gtk_range_set_value(GTK_RANGE(g_MainWindow.pZoomScale), map_get_zoomlevel(g_MainWindow.pMap));
+ g_signal_handlers_unblock_by_func(g_MainWindow.pZoomScale, mainwindow_on_zoomscale_value_changed, NULL);
+
+ mainwindow_statusbar_update_zoomscale();
}
void mainwindow_set_zoomlevel(gint nZoomLevel)
@@ -744,11 +766,10 @@ void mainwindow_set_zoomlevel(gint nZoomLevel)
map_set_zoomlevel(g_MainWindow.pMap, nZoomLevel);
// set zoomlevel scale but prevent it from calling handler (mainwindow_on_zoomscale_value_changed)
- g_signal_handlers_block_by_func(g_MainWindow.pZoomScale, mainwindow_on_zoomscale_value_changed, NULL);
- gtk_range_set_value(GTK_RANGE(g_MainWindow.pZoomScale), nZoomLevel);
- g_signal_handlers_unblock_by_func(g_MainWindow.pZoomScale, mainwindow_on_zoomscale_value_changed, NULL);
+// g_signal_handlers_block_by_func(g_MainWindow.pZoomScale, mainwindow_on_zoomscale_value_changed, NULL);
+// gtk_range_set_value(GTK_RANGE(g_MainWindow.pZoomScale), nZoomLevel);
+// g_signal_handlers_unblock_by_func(g_MainWindow.pZoomScale, mainwindow_on_zoomscale_value_changed, NULL);
- mainwindow_statusbar_update_zoomscale();
mainwindow_update_zoom_buttons();
}
@@ -771,11 +792,11 @@ void mainwindow_on_zoomscale_value_changed(GtkRange *range, gpointer user_data)
//
//
//
-static void gui_set_tool(EToolType eTool)
-{
- g_MainWindow.eSelectedTool = eTool;
- gdk_window_set_cursor(GTK_WIDGET(g_MainWindow.pDrawingArea)->window, g_Tools[eTool].Cursor.pGdkCursor);
-}
+// static void gui_set_tool(EToolType eTool)
+// {
+// g_MainWindow.eSelectedTool = eTool;
+// gdk_window_set_cursor(GTK_WIDGET(g_MainWindow.pDrawingArea)->window, g_Tools[eTool].Cursor.pGdkCursor);
+// }
//
// Callbacks for About box
@@ -859,11 +880,18 @@ void mainwindow_on_sidebarmenuitem_activate(GtkMenuItem *menuitem, gpointer user
mainwindow_set_sidebox_visible(!mainwindow_get_sidebox_visible());
}
+#define ZOOM_MAJOR_TICK_SIZE (4)
+
// Zoom buttons / menu items (shared callbacks)
void mainwindow_on_zoomin_activate(GtkMenuItem *menuitem, gpointer user_data)
{
+ gint nNewZoomLevel = map_get_zoomlevel(g_MainWindow.pMap) - 1; // XXX: make zoomlevel 0-based and the -1 will go away
+ nNewZoomLevel -= (nNewZoomLevel % ZOOM_MAJOR_TICK_SIZE);
+ nNewZoomLevel += ZOOM_MAJOR_TICK_SIZE;
+ nNewZoomLevel += 1; // XXX: make zoomlevel 0-based and the +1 will go away
+
// tell the map
- map_set_zoomlevel(g_MainWindow.pMap, map_get_zoomlevel(g_MainWindow.pMap) + 1);
+ map_set_zoomlevel(g_MainWindow.pMap, nNewZoomLevel);
// update the gui
mainwindow_set_zoomlevel(map_get_zoomlevel(g_MainWindow.pMap));
@@ -874,7 +902,12 @@ void mainwindow_on_zoomin_activate(GtkMenuItem *menuitem, gpointer user_data)
void mainwindow_on_zoomout_activate(GtkMenuItem *menuitem, gpointer user_data)
{
- map_set_zoomlevel(g_MainWindow.pMap, map_get_zoomlevel(g_MainWindow.pMap) - 1);
+ gint nNewZoomLevel = map_get_zoomlevel(g_MainWindow.pMap) - 1;
+ if((nNewZoomLevel % ZOOM_MAJOR_TICK_SIZE) == 0) nNewZoomLevel -= ZOOM_MAJOR_TICK_SIZE;
+ else nNewZoomLevel -= (nNewZoomLevel % ZOOM_MAJOR_TICK_SIZE);
+ nNewZoomLevel += 1; // XXX: make zoomlevel 0-based and the +1 will go away
+
+ map_set_zoomlevel(g_MainWindow.pMap, nNewZoomLevel);
mainwindow_set_zoomlevel(map_get_zoomlevel(g_MainWindow.pMap));
mainwindow_draw_map(DRAWFLAG_GEOMETRY);
mainwindow_set_draw_pretty_timeout(DRAW_PRETTY_ZOOM_TIMEOUT_MS);
@@ -974,6 +1007,11 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
gint nWidth = GTK_WIDGET(g_MainWindow.pDrawingArea)->allocation.width;
gint nHeight = GTK_WIDGET(g_MainWindow.pDrawingArea)->allocation.height;
+
+ // nX and nY clipped to screen
+ gint nClippedX = (nX < 0) ? 0 : (nX > nWidth) ? nWidth : nX;
+ gint nClippedY = (nY < 0) ? 0 : (nY > nHeight) ? nHeight : nY;
+
EDirection eScrollDirection = DIRECTION_NONE;
// get mouse position on screen
@@ -982,7 +1020,7 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
map_windowpoint_to_mappoint(g_MainWindow.pMap, &screenpoint, &mappoint);
maphit_t* pHitStruct = NULL;
- map_hit_test(g_MainWindow.pMap, &mappoint, &pHitStruct);
+ map_hittest(g_MainWindow.pMap, &mappoint, &pHitStruct);
// hitstruct free'd far below
if(event->button == MOUSE_BUTTON_LEFT) {
@@ -1004,6 +1042,14 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
mainwindow_set_scroll_timeout();
}
+ else if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_MainWindow.pZoomToolRadioButton))) {
+ //g_print("begin rect draw\n");
+ g_MainWindow.bDrawingZoomRect = TRUE;
+
+ // set both rect points to click point
+ g_MainWindow.rcZoomRect.A.nX = g_MainWindow.rcZoomRect.B.nX = nX;
+ g_MainWindow.rcZoomRect.A.nY = g_MainWindow.rcZoomRect.B.nY = nY;
+ }
else {
g_MainWindow.bMouseDragging = TRUE;
g_MainWindow.bMouseDragMovement = FALSE;
@@ -1048,6 +1094,27 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
}
}
+ if(g_MainWindow.bDrawingZoomRect == TRUE) {
+ if((map_screenrect_width(&(g_MainWindow.rcZoomRect)) > ZOOM_TOOL_THRESHOLD) && (map_screenrect_height(&(g_MainWindow.rcZoomRect)) > ZOOM_TOOL_THRESHOLD)) {
+ map_zoom_to_screenrect(g_MainWindow.pMap, &(g_MainWindow.rcZoomRect));
+
+ // update GUI
+ mainwindow_update_zoom_buttons();
+ mainwindow_statusbar_update_position();
+
+ mainwindow_draw_map(DRAWFLAG_GEOMETRY);
+ mainwindow_set_draw_pretty_timeout(DRAW_PRETTY_ZOOM_TIMEOUT_MS);
+ mainwindow_add_history();
+ }
+ else {
+ // Since we're not redrawing the map, we need to erase the selection rectangle
+ mainwindow_draw_xor_rect(&(g_MainWindow.rcZoomRect));
+ }
+
+ // all done
+ g_MainWindow.bDrawingZoomRect = FALSE;
+ }
+
// end scrolling, if active
if(g_MainWindow.bScrolling == TRUE) {
// NOTE: don't restore cursor (mouse could *still* be over screen edge)
@@ -1086,7 +1153,7 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
}
}
else if(event->type == GDK_2BUTTON_PRESS) {
- // can only double click in the middle (not on a scroll border)
+ // can only double-click in the middle (not on a scroll border)
eScrollDirection = match_border(nX, nY, nWidth, nHeight, BORDER_SCROLL_CLICK_TARGET_SIZE);
if(eScrollDirection == DIRECTION_NONE) {
animator_destroy(g_MainWindow.pAnimator);
@@ -1108,7 +1175,6 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
if(event->type == GDK_BUTTON_PRESS) {
GtkMenu* pMenu = g_MainWindow.pMapPopupMenu; // default to generic map popup
- g_print("here %s\n", pHitStruct);
if(pHitStruct != NULL) {
if(pHitStruct->eHitType == MAP_HITTYPE_LOCATION) {
// Use POI specific popup menu
@@ -1134,7 +1200,7 @@ static gboolean mainwindow_on_mouse_button_click(GtkWidget* w, GdkEventButton *e
//gtk_menu_popup(pMenu, NULL, NULL, NULL, NULL, event->button, event->time);
}
}
- map_hitstruct_free(g_MainWindow.pMap, pHitStruct);
+ map_hittest_maphit_free(g_MainWindow.pMap, pHitStruct);
return TRUE;
}
@@ -1154,7 +1220,17 @@ static gboolean mainwindow_on_mouse_motion(GtkWidget* w, GdkEventMotion *event)
gint nWidth = GTK_WIDGET(g_MainWindow.pDrawingArea)->allocation.width;
gint nHeight = GTK_WIDGET(g_MainWindow.pDrawingArea)->allocation.height;
- gint nCursor = GDK_LEFT_PTR;
+ // nX and nY clipped to screen
+ gint nClippedX = (nX < 0) ? 0 : ((nX > nWidth-1) ? nWidth-1 : nX);
+ gint nClippedY = (nY < 0) ? 0 : ((nY > nHeight-1) ? nHeight-1 : nY);
+
+ gint nCursor;
+ if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_MainWindow.pZoomToolRadioButton))) {
+ nCursor = GDK_SIZING;
+ }
+ else {
+ nCursor = GDK_LEFT_PTR;
+ }
if(g_MainWindow.bMouseDragging) {
g_MainWindow.bMouseDragMovement = TRUE;
@@ -1183,6 +1259,15 @@ static gboolean mainwindow_on_mouse_motion(GtkWidget* w, GdkEventMotion *event)
// update direction if actively scrolling
g_MainWindow.eScrollDirection = eScrollDirection;
}
+ else if(g_MainWindow.bDrawingZoomRect) {
+ //g_print("updating rect\n");
+ mainwindow_draw_xor_rect(&g_MainWindow.rcZoomRect); // erase old rect (XOR operator rocks!)
+
+ g_MainWindow.rcZoomRect.B.nX = nClippedX;
+ g_MainWindow.rcZoomRect.B.nY = nClippedY;
+
+ mainwindow_draw_xor_rect(&g_MainWindow.rcZoomRect); // draw new rect
+ }
else {
// If not dragging or scrolling, user is just moving mouse around.
// Update tooltip and mouse cursor based on what we're pointing at.
@@ -1199,7 +1284,7 @@ static gboolean mainwindow_on_mouse_motion(GtkWidget* w, GdkEventMotion *event)
// try to "hit" something on the map. a road, a location, whatever!
maphit_t* pHitStruct = NULL;
- if(map_hit_test(g_MainWindow.pMap, &mappoint, &pHitStruct)) {
+ if(map_hittest(g_MainWindow.pMap, &mappoint, &pHitStruct)) {
// A hit! Move the tooltip here, format the text, and show it.
tooltip_set_upper_left_corner(g_MainWindow.pTooltip, (gint)(event->x_root) + TOOLTIP_OFFSET_X, (gint)(event->y_root) + TOOLTIP_OFFSET_Y);
@@ -1240,7 +1325,7 @@ static gboolean mainwindow_on_mouse_motion(GtkWidget* w, GdkEventMotion *event)
g_print("url: %s\n", pHitStruct->URLHit.pszURL);
}
- map_hitstruct_free(g_MainWindow.pMap, pHitStruct);
+ map_hittest_maphit_free(g_MainWindow.pMap, pHitStruct);
}
else {
// no hit. hide the tooltip
@@ -1293,14 +1378,27 @@ static gboolean mainwindow_on_mouse_scroll(GtkWidget* w, GdkEventScroll *event)
}
}
-static gboolean mainwindow_on_key_press(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
+static gboolean mainwindow_on_key_press(GtkWidget *widget, GdkEventKey *pEvent, gpointer user_data)
{
- g_print("key_press\n");
+ //g_print("key_press\n");
+ if(pEvent->keyval == GDK_F1) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_MainWindow.pPointerToolRadioButton), TRUE);
+ }
+ else if(pEvent->keyval == GDK_F2) {
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_MainWindow.pZoomToolRadioButton), TRUE);
+ }
+ else if(pEvent->keyval == GDK_Escape) {
+ if(g_MainWindow.bDrawingZoomRect == TRUE) {
+ // cancel zoom-rect
+ mainwindow_draw_xor_rect(&(g_MainWindow.rcZoomRect));
+ g_MainWindow.bDrawingZoomRect = FALSE;
+ }
+ }
return FALSE;
}
static gboolean mainwindow_on_key_release(GtkWidget *widget, GdkEventKey *event, gpointer user_data)
{
- g_print("key_release\n");
+ //g_print("key_release\n");
return FALSE;
}
diff --git a/src/map.c b/src/map.c
index bd99259..23ffcb3 100644
--- a/src/map.c
+++ b/src/map.c
@@ -22,12 +22,12 @@
*/
#include <gdk/gdkx.h>
-//#include <cairo.h>
#include <gtk/gtk.h>
#include <math.h>
#include "main.h"
#include "map_style.h"
+#include "map_tilemanager.h"
#include "gui.h"
#include "map.h"
#include "mainwindow.h"
@@ -59,32 +59,16 @@
// D) The current value is the result of some casual testing.
// XXX: The names below aren't very clear.
-#define TILE_SHIFT (1000.0) // the units we care about (1000ths of a degree)
-#define TILE_MODULUS (23) // how many of the above units each tile is on a side
-#define MAP_TILE_WIDTH (TILE_MODULUS / TILE_SHIFT) // width and height of a tile, in degrees
+// #define TILE_SHIFT (1000.0)
+// #define TILE_MODULUS (23)
+// #define TILE_WIDTH (TILE_MODULUS / TILE_SHIFT)
#define MIN_ROAD_HIT_TARGET_WIDTH (6) // make super thin roads a bit easier to hover over/click, in pixels
-#define MIN_ZOOMLEVEL_FOR_LOCATIONS (6)
/* Prototypes */
static void map_init_location_hash(map_t* pMap);
-// data loading
-static gboolean map_data_load_tiles(map_t* pMap, maprect_t* pRect);
-static gboolean map_data_load_geometry(map_t* pMap, maprect_t* pRect);
-static gboolean map_data_load_locations(map_t* pMap, maprect_t* pRect);
-
-
-// hit testing
-static gboolean map_hit_test_layer_roads(GPtrArray* pPointStringsArray, gdouble fMaxDistance, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
-static gboolean map_hit_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pHitPoint, gdouble fMaxDistance, mappoint_t* pReturnClosestPoint, gdouble* pfReturnPercentAlongLine);
-static ESide map_side_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pClosestPointOnLine, mappoint_t* pHitPoint);
-static gboolean map_hit_test_locationselections(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationSelectionsArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
-
-static gboolean map_hit_test_locationsets(map_t* pMap, rendermetrics_t* pRenderMetrics, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
-static gboolean map_hit_test_locations(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationsArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
-
static void map_store_location(map_t* pMap, location_t* pLocation, gint nLocationSetID);
static void map_data_clear(map_t* pMap);
@@ -93,22 +77,65 @@ void map_get_render_metrics(map_t* pMap, rendermetrics_t* pMetrics);
gdouble map_get_straight_line_distance_in_degrees(mappoint_t* p1, mappoint_t* p2);
// Each zoomlevel has a scale and an optional name (name isn't used for anything)
-zoomlevel_t g_sZoomLevels[NUM_ZOOM_LEVELS+1] = {
- {1,0,0,0,0,"undefined"}, // no zoom level 0
-
-// { 1600000, ""},
-// { 800000, ""},
-// { 400000, ""},
-// { 200000, ""},
-// { 100000, ""},
-
- { 80000, UNIT_MILES, 2, UNIT_KILOMETERS, 2, "", }, // 1
- { 40000, UNIT_MILES, 1, UNIT_KILOMETERS, 1, "", }, // 2
- { 20000, UNIT_FEET, 2000, UNIT_METERS, 400, "", }, // 3
- { 10000, UNIT_FEET, 1000, UNIT_METERS, 200, "", }, // 4
- { 5000, UNIT_FEET, 500, UNIT_METERS, 100, "", }, // 5
+zoomlevel_t g_sZoomLevels[NUM_ZOOM_LEVELS] = {
+ // 1.166144850000 magic number for 2000-80000 in 25 steps. (each scale is previous * this #)
+ // 1.181891000000 magic number for 2000-1,600,000 in 41 steps (11 major with 3 minor in between)
+
+ {150000000, UNIT_MILES,2000,UNIT_KILOMETERS,2000, 1, 3}, // *1
+ {123000000, UNIT_MILES,1000,UNIT_KILOMETERS,48, 1, 3}, // 2
+ { 96000000, UNIT_MILES,500, UNIT_KILOMETERS,48, 1, 3}, // 3
+ { 69000000, UNIT_MILES,200, UNIT_KILOMETERS,48, 1, 3}, // 4
+
+ { 42000000, UNIT_MILES,100, UNIT_KILOMETERS,24, 1, 3}, // *5
+ { 35000000, UNIT_MILES,50, UNIT_KILOMETERS,24, 1, 3}, // 6
+ { 28000000, UNIT_MILES,20, UNIT_KILOMETERS,24, 1, 3}, // 7
+ { 21000000, UNIT_MILES,10, UNIT_KILOMETERS,24, 1, 3}, // 8
+
+ { 14000000, UNIT_MILES,10, UNIT_KILOMETERS,12, 2, 3}, // *9
+ { 11600000, UNIT_MILES,10, UNIT_KILOMETERS,12, 2, 3}, // 10
+ { 9200000, UNIT_MILES, 5, UNIT_KILOMETERS, 7, 2, 3}, // 11
+ { 6800000, UNIT_MILES, 5, UNIT_KILOMETERS, 7, 2, 3}, // 12
+
+ { 4400000, UNIT_MILES, 5, UNIT_KILOMETERS, 7, 3, 2}, // *13
+ { 3850000, UNIT_MILES, 5, UNIT_KILOMETERS, 7, 3, 2}, // 14
+ { 3300000, UNIT_MILES, 5, UNIT_KILOMETERS, 7, 3, 2}, // 15
+ { 2750000, UNIT_MILES, 5, UNIT_KILOMETERS, 7, 3, 2}, // 16
+
+ { 2200000, UNIT_MILES, 2, UNIT_KILOMETERS, 2, 4, 2}, // *17
+ { 1832250, UNIT_MILES, 2, UNIT_KILOMETERS, 2, 4, 2}, // 18
+ { 1464500, UNIT_MILES, 2, UNIT_KILOMETERS, 2, 4, 2}, // 19
+ { 1100000, UNIT_MILES, 2, UNIT_KILOMETERS, 2, 4, 2}, // 20
+
+ { 729000, UNIT_MILES, 2, UNIT_KILOMETERS, 2, 5, 1}, // *21
+ { 607500, UNIT_MILES, 1, UNIT_KILOMETERS, 1, 5, 1}, // 22
+ { 486000, UNIT_MILES, 1, UNIT_KILOMETERS, 1, 5, 1}, // 23
+ { 364500, UNIT_MILES, 1, UNIT_KILOMETERS, 1, 5, 1}, // 24
+
+ { 243000, UNIT_MILES, 1, UNIT_KILOMETERS, 1, 6, 1}, // *25
+ { 202500, UNIT_FEET, 3000,UNIT_METERS, 500, 6, 1}, // 26
+ { 162000, UNIT_FEET, 2000,UNIT_METERS, 500, 6, 1}, // 27
+ { 121500, UNIT_FEET, 2000,UNIT_METERS, 500, 6, 1}, // 28
+
+ { 81000, UNIT_FEET, 2000,UNIT_METERS, 300, 7, 0}, // *29
+ { 67500, UNIT_FEET, 2000,UNIT_METERS, 300, 7, 0}, // 30
+ { 54000, UNIT_FEET, 2000,UNIT_METERS, 300, 7, 0}, // 31
+ { 40500, UNIT_FEET, 1000,UNIT_METERS, 200, 7, 0}, // 32
+
+ { 27000, UNIT_FEET, 1000,UNIT_METERS, 200, 8, 0}, // *33
+ { 22500, UNIT_FEET, 1000,UNIT_METERS, 200, 8, 0}, // 34
+ { 18000, UNIT_FEET, 1000,UNIT_METERS, 200, 8, 0}, // 35
+ { 13500, UNIT_FEET, 500, UNIT_METERS, 100, 8, 0}, // 36
+
+ { 9000, UNIT_FEET, 500, UNIT_METERS, 100, 9, 0}, // *37
+ { 7500, UNIT_FEET, 500, UNIT_METERS, 100, 9, 0}, // 38
+ { 6000, UNIT_FEET, 300, UNIT_METERS, 50, 9, 0}, // 39
+ { 4500, UNIT_FEET, 300, UNIT_METERS, 50, 9, 0}, // 40
+
+ { 3000, UNIT_FEET, 300, UNIT_METERS, 50, 10, 0}, // *41
};
+#define MIN_ZOOMLEVEL_FOR_LOCATIONS (6)
+
gchar* g_apszMapObjectTypeNames[] = { // XXX: would be nice to remove this. although we *do* need to maintain a link between imported data and styles
"",
"minor-roads",
@@ -132,6 +159,7 @@ gchar* g_apszMapObjectTypeNames[] = { // XXX: would be nice to remove this. alth
// init the module
void map_init(void)
{
+ g_print("*********************************** %f\n", WORLD_FEET_PER_DEGREE);
}
gboolean map_new(map_t** ppMap, GtkWidget* pTargetWidget)
@@ -143,9 +171,9 @@ gboolean map_new(map_t** ppMap, GtkWidget* pTargetWidget)
map_t* pMap = g_new0(map_t, 1);
// Create array of Track handles
- pMap->pTracksArray = g_array_new(FALSE, /* not zero-terminated */
- TRUE, /* clear to 0 (?) */
- sizeof(gint));
+// pMap->pTracksArray = g_array_new(FALSE, /* not zero-terminated */
+// TRUE, /* clear to 0 (?) */
+// sizeof(gint));
map_init_location_hash(pMap);
@@ -154,15 +182,17 @@ gboolean map_new(map_t** ppMap, GtkWidget* pTargetWidget)
scenemanager_new(&(pMap->pSceneManager));
g_assert(pMap->pSceneManager);
- pMap->uZoomLevel = 1; // XXX: better way to init this? or should we force the GUI to reflect this #?
+ pMap->uZoomLevel = 1;
// init containers for geometry data
- gint i;
- for(i=0 ; i<G_N_ELEMENTS(pMap->apLayerData) ; i++) {
- maplayer_data_t* pLayer = g_new0(maplayer_data_t, 1);
- pLayer->pRoadsArray = g_ptr_array_new();
- pMap->apLayerData[i] = pLayer;
- }
+// gint i;
+// for(i=0 ; i<G_N_ELEMENTS(pMap->apLayerData) ; i++) {
+// maplayer_data_t* pLayer = g_new0(maplayer_data_t, 1);
+// pLayer->pRoadsArray = g_ptr_array_new();
+// pMap->apLayerData[i] = pLayer;
+// }
+
+ pMap->pTileManager = map_tilemanager_new();
// init POI selection
pMap->pLocationSelectionArray = g_ptr_array_new();
@@ -184,8 +214,8 @@ void map_draw(map_t* pMap, GdkPixmap* pTargetPixmap, gint nDrawFlags)
// Load geometry
TIMER_BEGIN(loadtimer, "--- BEGIN ALL DB LOAD");
- map_data_clear(pMap);
- map_data_load_tiles(pMap, &(pRenderMetrics->rWorldBoundingBox));
+// map_data_clear(pMap);
+ GPtrArray* pTileArray = map_tilemanager_load_tiles_for_worldrect(pMap->pTileManager, &(pRenderMetrics->rWorldBoundingBox), pRenderMetrics->nLevelOfDetail);
TIMER_END(loadtimer, "--- END ALL DB LOAD");
scenemanager_clear(pMap->pSceneManager);
@@ -205,15 +235,15 @@ void map_draw(map_t* pMap, GdkPixmap* pTargetPixmap, gint nDrawFlags)
if(nRenderMode == RENDERMODE_FAST) {
//
if(nDrawFlags & DRAWFLAG_GEOMETRY) {
- map_draw_gdk(pMap, pRenderMetrics, pTargetPixmap, DRAWFLAG_GEOMETRY);
+ map_draw_gdk(pMap, pTileArray, pRenderMetrics, pTargetPixmap, DRAWFLAG_GEOMETRY);
nDrawFlags &= ~DRAWFLAG_GEOMETRY;
}
// Call cairo for finishing the scene
- map_draw_cairo(pMap, pRenderMetrics, pTargetPixmap, nDrawFlags);
+ map_draw_cairo(pMap, pTileArray, pRenderMetrics, pTargetPixmap, nDrawFlags);
}
else { // nRenderMode == RENDERMODE_PRETTY
- map_draw_cairo(pMap, pRenderMetrics, pTargetPixmap, nDrawFlags);
+ map_draw_cairo(pMap, pTileArray, pRenderMetrics, pTargetPixmap, nDrawFlags);
}
#ifdef ENABLE_SCENEMANAGER_DEBUG_TEST
@@ -222,6 +252,8 @@ void map_draw(map_t* pMap, GdkPixmap* pTargetPixmap, gint nDrawFlags)
#endif
gtk_widget_queue_draw(pMap->pTargetWidget);
+
+ g_ptr_array_free(pTileArray, TRUE);
}
// ========================================================
@@ -247,10 +279,10 @@ void map_set_zoomlevel(map_t* pMap, guint16 uZoomLevel)
if(uZoomLevel > MAX_ZOOM_LEVEL) uZoomLevel = MAX_ZOOM_LEVEL;
else if(uZoomLevel < MIN_ZOOM_LEVEL) uZoomLevel = MIN_ZOOM_LEVEL;
- if(uZoomLevel != pMap->uZoomLevel) {
+// if(uZoomLevel != pMap->uZoomLevel) {
pMap->uZoomLevel = uZoomLevel;
// map_set_redraw_needed(TRUE);
- }
+// }
}
guint16 map_get_zoomlevel(const map_t* pMap)
@@ -260,7 +292,7 @@ guint16 map_get_zoomlevel(const map_t* pMap)
guint32 map_get_zoomlevel_scale(const map_t* pMap)
{
- return g_sZoomLevels[pMap->uZoomLevel].uScale; // returns "5000" for 1:5000 scale
+ return g_sZoomLevels[pMap->uZoomLevel-1].uScale; // returns "5000" for 1:5000 scale
}
gboolean map_can_zoom_in(const map_t* pMap)
@@ -273,70 +305,6 @@ gboolean map_can_zoom_out(const map_t* pMap)
return (pMap->uZoomLevel > MIN_ZOOM_LEVEL);
}
-// ========================================================
-// Coordinate Conversion Functions
-// ========================================================
-
-gchar* g_aDistanceUnitNames[] = {
- "Feet",
- "Miles",
- "Meters",
- "Kilometers",
-};
-
-gdouble map_distance_in_units_to_degrees(map_t* pMap, gdouble fDistance, gint nDistanceUnit)
-{
- switch(nDistanceUnit) {
- case UNIT_FEET:
- return WORLD_FEET_TO_DEGREES(fDistance);
- case UNIT_MILES:
- return WORLD_MILES_TO_DEGREES(fDistance);
- case UNIT_METERS:
- return WORLD_METERS_TO_DEGREES(fDistance);
- case UNIT_KILOMETERS:
- return WORLD_KILOMETERS_TO_DEGREES(fDistance);
- default:
- g_warning("UNKNOWN DISTANCE UNIT (%d)\n", nDistanceUnit);
- return 0;
- }
-}
-
-// convert pixels to a span of degrees
-double map_pixels_to_degrees(map_t* pMap, gint16 nPixels, guint16 uZoomLevel)
-{
- double fMonitorPixelsPerInch = 85.333; // XXX: don't hardcode this
- double fPixelsPerMeter = fMonitorPixelsPerInch * INCHES_PER_METER;
- double fMetersOfPixels = ((float)nPixels) / fPixelsPerMeter;
-
- // If we had 3 meters of pixels (a very big monitor:) and the scale was 1000:1 then
- // we would want to show 3000 meters worth of world space
- double fMetersOfWorld = ((float)g_sZoomLevels[uZoomLevel].uScale) * fMetersOfPixels;
-
- return WORLD_METERS_TO_DEGREES(fMetersOfWorld);
-}
-
-double map_degrees_to_pixels(map_t* pMap, gdouble fDegrees, guint16 uZoomLevel)
-{
- double fMonitorPixelsPerInch = 85.333; // XXX: don't hardcode this
-
- double fResultInMeters = WORLD_DEGREES_TO_METERS(fDegrees);
- double fResultInPixels = (INCHES_PER_METER * fResultInMeters) * fMonitorPixelsPerInch;
- fResultInPixels /= (float)g_sZoomLevels[uZoomLevel].uScale;
- return fResultInPixels;
-}
-
-void map_windowpoint_to_mappoint(map_t* pMap, screenpoint_t* pScreenPoint, mappoint_t* pMapPoint)
-{
- // Calculate the # of pixels away from the center point the click was
- gint16 nPixelDeltaX = (gint)(pScreenPoint->nX) - (pMap->MapDimensions.uWidth / 2);
- gint16 nPixelDeltaY = (gint)(pScreenPoint->nY) - (pMap->MapDimensions.uHeight / 2);
-
- // Convert pixels to world coordinates
- pMapPoint->fLongitude = pMap->MapCenter.fLongitude + map_pixels_to_degrees(pMap, nPixelDeltaX, pMap->uZoomLevel);
- // reverse the X, clicking above
- pMapPoint->fLatitude = pMap->MapCenter.fLatitude - map_pixels_to_degrees(pMap, nPixelDeltaY, pMap->uZoomLevel);
-}
-
// Call this to pan around the map
void map_center_on_windowpoint(map_t* pMap, guint16 uX, guint16 uY)
{
@@ -345,9 +313,9 @@ void map_center_on_windowpoint(map_t* pMap, guint16 uX, guint16 uY)
gint16 nPixelDeltaY = uY - (pMap->MapDimensions.uHeight / 2);
// Convert pixels to world coordinates
- double fWorldDeltaX = map_pixels_to_degrees(pMap, nPixelDeltaX, pMap->uZoomLevel);
+ gdouble fWorldDeltaX = map_pixels_to_degrees(pMap, nPixelDeltaX, pMap->uZoomLevel);
// reverse the X, clicking above
- double fWorldDeltaY = -map_pixels_to_degrees(pMap, nPixelDeltaY, pMap->uZoomLevel);
+ gdouble fWorldDeltaY = -map_pixels_to_degrees(pMap, nPixelDeltaY, pMap->uZoomLevel);
// g_message("panning %d,%d pixels (%.10f,%.10f world coords)\n", nPixelDeltaX, nPixelDeltaY, fWorldDeltaX, fWorldDeltaY);
@@ -357,6 +325,41 @@ void map_center_on_windowpoint(map_t* pMap, guint16 uX, guint16 uY)
map_set_centerpoint(pMap, &pt);
}
+void map_zoom_to_screenrect(map_t* pMap, const screenrect_t* pRect)
+{
+ // recenter on rect
+ screenpoint_t ptCenter;
+ map_get_screenrect_centerpoint(pRect, &ptCenter);
+ map_center_on_windowpoint(pMap, ptCenter.nX, ptCenter.nY);
+ //g_print("box centerpoint = %d,%d\n", ptCenter.nX, ptCenter.nY);
+
+ // calculate size of rectangle in degrees
+ gdouble fBoxLongitude = map_pixels_to_degrees(pMap, map_screenrect_width(pRect), pMap->uZoomLevel);
+ gdouble fBoxLatitude = map_pixels_to_degrees(pMap, map_screenrect_height(pRect), pMap->uZoomLevel);
+ //g_print("box size pixels = %d,%d\n", map_screenrect_width(pRect), map_screenrect_height(pRect));
+ //g_print("box size = %f,%f\n", fBoxLongitude, fBoxLatitude);
+
+ // go from zoomed all the way to all the way out, looking for a rectangle
+ // that is wide & tall enough to show all of pRect
+ gboolean bFound = FALSE;
+ gint nZoomLevel;
+ for(nZoomLevel = MAX_ZOOM_LEVEL ; nZoomLevel >= MIN_ZOOM_LEVEL ; nZoomLevel--) {
+ pMap->uZoomLevel = nZoomLevel;
+
+ rendermetrics_t metrics;
+ map_get_render_metrics(pMap, &metrics);
+ //g_print("Screen at zoomlevel %d = %f,%f\n", nZoomLevel, metrics.fScreenLatitude, metrics.fScreenLongitude);
+
+ if(fBoxLatitude < metrics.fScreenLatitude && fBoxLongitude < metrics.fScreenLongitude) {
+ //g_print("Success\n");
+ break;
+ }
+ }
+ // either we hit the 'break' and left pMap->uZoomLevel with the appropriate zoomlevel, or we
+ // finished the loop and left pMap->uZoomLevel with MIN_ZOOM_LEVEL (farthest from the ground)
+ // which is the best we can do. either way we recentered.
+}
+
void map_set_centerpoint(map_t* pMap, const mappoint_t* pPoint)
{
g_assert(pPoint != NULL);
@@ -386,10 +389,7 @@ void map_set_dimensions(map_t* pMap, const dimensions_t* pDimensions)
// XXX: free old pixmap?
//g_assert(pMap->pPixmap == NULL);
- pMap->pPixmap = gdk_pixmap_new(
- pMap->pTargetWidget->window,
- pMap->MapDimensions.uWidth, pMap->MapDimensions.uHeight,
- -1);
+ pMap->pPixmap = gdk_pixmap_new(pMap->pTargetWidget->window, pMap->MapDimensions.uWidth, pMap->MapDimensions.uHeight, -1);
}
// ========================================================
@@ -401,13 +401,16 @@ void map_get_render_metrics(map_t* pMap, rendermetrics_t* pMetrics)
g_assert(pMetrics != NULL);
//
- // Set up renderMetrics array
+ // Copy a few values in
//
pMetrics->nZoomLevel = map_get_zoomlevel(pMap);
pMetrics->nWindowWidth = pMap->MapDimensions.uWidth;
pMetrics->nWindowHeight = pMap->MapDimensions.uHeight;
+ pMetrics->nLevelOfDetail = g_sZoomLevels[pMetrics->nZoomLevel-1].nLevelOfDetail;
+ //
// Calculate how many world degrees we'll be drawing
+ //
pMetrics->fScreenLatitude = map_pixels_to_degrees(pMap, pMap->MapDimensions.uHeight, pMetrics->nZoomLevel);
pMetrics->fScreenLongitude = map_pixels_to_degrees(pMap, pMap->MapDimensions.uWidth, pMetrics->nZoomLevel);
@@ -418,798 +421,50 @@ void map_get_render_metrics(map_t* pMap, rendermetrics_t* pMetrics)
pMetrics->rWorldBoundingBox.B.fLatitude = pMap->MapCenter.fLatitude + pMetrics->fScreenLatitude/2;
}
-static gboolean map_data_load_tiles(map_t* pMap, maprect_t* pRect)
-{
-// g_print("*****\n"
-// "rect is (%f,%f)(%f,%f)\n", pRect->A.fLatitude,pRect->A.fLongitude, pRect->B.fLatitude,pRect->B.fLongitude);
- gint32 nLatStart = (gint32)(pRect->A.fLatitude * TILE_SHIFT);
- // round it DOWN (south)
- if(pRect->A.fLatitude > 0) {
- nLatStart -= (nLatStart % TILE_MODULUS);
- }
- else {
- nLatStart -= (nLatStart % TILE_MODULUS);
- nLatStart -= TILE_MODULUS;
- }
-
- gint32 nLonStart = (gint32)(pRect->A.fLongitude * TILE_SHIFT);
- // round it DOWN (west)
- if(pRect->A.fLongitude > 0) {
- nLonStart -= (nLonStart % TILE_MODULUS);
- }
- else {
- nLonStart -= (nLonStart % TILE_MODULUS);
- nLonStart -= TILE_MODULUS;
- }
+// void map_add_track(map_t* pMap, gint hTrack)
+// {
+// g_array_append_val(pMap->pTracksArray, hTrack);
+// }
- gint32 nLatEnd = (gint32)(pRect->B.fLatitude * TILE_SHIFT);
- // round it UP (north)
- if(pRect->B.fLatitude > 0) {
- nLatEnd -= (nLatEnd % TILE_MODULUS);
- nLatEnd += TILE_MODULUS;
- }
- else {
- nLatEnd -= (nLatEnd % TILE_MODULUS);
- }
-
- gint32 nLonEnd = (gint32)(pRect->B.fLongitude * TILE_SHIFT);
- // round it UP (east)
- if(pRect->B.fLongitude > 0) {
- nLonEnd -= (nLonEnd % TILE_MODULUS);
- nLonEnd += TILE_MODULUS;
- }
- else {
- nLonEnd -= (nLonEnd % TILE_MODULUS);
- }
-
- // how many tiles are we loading in each direction? (nice and safe as integer math...)
- gint nLatNumTiles = (nLatEnd - nLatStart) / TILE_MODULUS;
- gint nLonNumTiles = (nLonEnd - nLonStart) / TILE_MODULUS;
-
- gdouble fLatStart = (gdouble)nLatStart / TILE_SHIFT;
- gdouble fLonStart = (gdouble)nLonStart / TILE_SHIFT;
-
- if(fLatStart > pRect->A.fLatitude) {
- g_print("fLatStart %f > pRect->A.fLatitude %f\n", fLatStart, pRect->A.fLatitude);
- g_assert(fLatStart <= pRect->A.fLatitude);
- }
- if(fLonStart > pRect->A.fLongitude) {
- g_print("fLonStart %f > pRect->A.fLongitude %f!!\n", fLonStart, pRect->A.fLongitude);
- g_assert_not_reached();
- }
-
- gint nLat,nLon;
- for(nLat = 0 ; nLat < nLatNumTiles ; nLat++) {
- for(nLon = 0 ; nLon < nLonNumTiles ; nLon++) {
-
- maprect_t rect;
- rect.A.fLatitude = fLatStart + ((gdouble)(nLat) * MAP_TILE_WIDTH);
- rect.A.fLongitude = fLonStart + ((gdouble)(nLon) * MAP_TILE_WIDTH);
- rect.B.fLatitude = fLatStart + ((gdouble)(nLat+1) * MAP_TILE_WIDTH);
- rect.B.fLongitude = fLonStart + ((gdouble)(nLon+1) * MAP_TILE_WIDTH);
-
- map_data_load_geometry(pMap, &rect);
- map_data_load_locations(pMap, &rect);
- }
- }
-}
-
-static gboolean map_enhance_linestring(GPtrArray* pSourceArray, GPtrArray* pDestArray, gboolean (*callback_alloc_point)(mappoint_t**), gdouble fMaxDistanceBetweenPoints, gdouble fMaxRandomDistance)
-{
- g_assert(pSourceArray->len >= 2);
-//g_print("pSourceArray->len = %d\n", pSourceArray->len);
-
- // add first point
- g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, 0));
-
- gint i = 0;
- for(i=0 ; i<(pSourceArray->len-1) ; i++) {
- mappoint_t* pPoint1 = g_ptr_array_index(pSourceArray, i);
- mappoint_t* pPoint2 = g_ptr_array_index(pSourceArray, i+1);
-
- gdouble fRise = (pPoint2->fLatitude - pPoint1->fLatitude);
- gdouble fRun = (pPoint2->fLongitude - pPoint1->fLongitude);
-
- gdouble fLineLength = sqrt((fRun*fRun) + (fRise*fRise));
-// g_print("fLineLength = %f\n", fLineLength);
- gint nNumMiddlePoints = (gint)(fLineLength / fMaxDistanceBetweenPoints);
-// g_print("(fLineLength / fMaxDistanceBetweenPoints) = nNumNewPoints; %f / %f = %d\n", fLineLength, fMaxDistanceBetweenPoints, nNumNewPoints);
- if(nNumMiddlePoints == 0) continue; // nothing to add
-
-// g_print("fDistanceBetweenPoints = %f\n", fDistanceBetweenPoints);
-
- gdouble fNormalizedX = fRun / fLineLength;
- gdouble fNormalizedY = fRise / fLineLength;
-// g_print("fNormalizedX = %f\n", fNormalizedX);
-// g_print("fNormalizedY = %f\n", fNormalizedY);
-
- gdouble fPerpendicularNormalizedX = fRise / fLineLength;
- gdouble fPerpendicularNormalizedY = -(fRun / fLineLength);
-
- // add points along the line
- gdouble fDistanceBetweenPoints = fLineLength / (gdouble)(nNumMiddlePoints + 1);
-
- gint j;
- for(j=0 ; j<nNumMiddlePoints ; j++) {
- mappoint_t* pNewPoint = NULL;
- callback_alloc_point(&pNewPoint);
- gdouble fDistanceFromPoint1 = (j+1) * fDistanceBetweenPoints;
-
- pNewPoint->fLongitude = pPoint1->fLongitude + (fDistanceFromPoint1 * fNormalizedX);
- pNewPoint->fLatitude = pPoint1->fLatitude + (fDistanceFromPoint1 * fNormalizedY);
-
- gdouble fRandomMovementLength = fMaxRandomDistance * g_random_double_range(-1.0, 1.0);
- pNewPoint->fLongitude += (fPerpendicularNormalizedX * fRandomMovementLength); // move each component
- pNewPoint->fLatitude += (fPerpendicularNormalizedY * fRandomMovementLength);
-
- g_ptr_array_add(pDestArray, pNewPoint);
- }
- }
- // add last point
- g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, pSourceArray->len-1));
-//g_print("pDestArray->len = %d\n", pDestArray->len);
-}
-
-static gboolean map_data_load_geometry(map_t* pMap, maprect_t* pRect)
-{
- g_return_val_if_fail(pMap != NULL, FALSE);
- g_return_val_if_fail(pRect != NULL, FALSE);
-
- db_resultset_t* pResultSet = NULL;
- db_row_t aRow;
-
- gint nZoomLevel = map_get_zoomlevel(pMap);
-
- TIMER_BEGIN(mytimer, "BEGIN Geometry LOAD");
-
- // generate SQL
- gchar azCoord1[20], azCoord2[20], azCoord3[20], azCoord4[20], azCoord5[20], azCoord6[20], azCoord7[20], azCoord8[20];
- gchar* pszSQL;
- pszSQL = g_strdup_printf(
- "SELECT Road.ID, Road.TypeID, AsBinary(Road.Coordinates), RoadName.Name, RoadName.SuffixID, AddressLeftStart, AddressLeftEnd, AddressRightStart, AddressRightEnd"
- " FROM Road "
- " LEFT JOIN RoadName ON (Road.RoadNameID=RoadName.ID)"
- " WHERE"
- " MBRIntersects(GeomFromText('Polygon((%s %s,%s %s,%s %s,%s %s,%s %s))'), Coordinates)"
- ,
- // upper left
- g_ascii_dtostr(azCoord1, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord2, 20, pRect->A.fLongitude),
- // upper right
- g_ascii_dtostr(azCoord3, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord4, 20, pRect->B.fLongitude),
- // bottom right
- g_ascii_dtostr(azCoord5, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord6, 20, pRect->B.fLongitude),
- // bottom left
- g_ascii_dtostr(azCoord7, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord8, 20, pRect->A.fLongitude),
- // upper left again
- azCoord1, azCoord2);
-
- //g_print("sql: %s\n", pszSQL);
-
- db_query(pszSQL, &pResultSet);
- g_free(pszSQL);
-
- TIMER_SHOW(mytimer, "after query");
-
- guint32 uRowCount = 0;
- if(pResultSet) {
- while((aRow = db_fetch_row(pResultSet))) {
- uRowCount++;
-
- // aRow[0] is ID
- // aRow[1] is TypeID
- // aRow[2] is Coordinates in mysql's text format
- // aRow[3] is road name
- // aRow[4] is road name suffix id
- // aRow[5] is road address left start
- // aRow[6] is road address left end
- // aRow[7] is road address right start
- // aRow[8] is road address right end
-
- // Get layer type that this belongs on
- gint nTypeID = atoi(aRow[1]);
- if(nTypeID < MAP_OBJECT_TYPE_FIRST || nTypeID > MAP_OBJECT_TYPE_LAST) {
- g_warning("geometry record '%s' has bad type '%s'\n", aRow[0], aRow[1]);
- continue;
- }
-
- //road_t* pNewRoad = NULL;
- //road_alloc(&pNewRoad);
- road_t* pNewRoad = g_new0(road_t, 1);
-
- // Build name by adding suffix, if one is present
- if(aRow[3] != NULL && aRow[4] != NULL) {
- const gchar* pszSuffix = road_suffix_itoa(atoi(aRow[4]), ROAD_SUFFIX_LENGTH_SHORT);
- pNewRoad->pszName = g_strdup_printf("%s%s%s", aRow[3], (pszSuffix[0] != '\0') ? " " : "", pszSuffix);
- }
- else {
- pNewRoad->pszName = g_strdup(""); // XXX: could we maybe not do this?
- }
- pNewRoad->nAddressLeftStart = atoi(aRow[5]);
- pNewRoad->nAddressLeftEnd = atoi(aRow[6]);
- pNewRoad->nAddressRightStart = atoi(aRow[7]);
- pNewRoad->nAddressRightEnd = atoi(aRow[8]);
-
- // perhaps let the wkb parser create the array (at the perfect size)
- pNewRoad->pMapPointsArray = g_array_new(FALSE, FALSE, sizeof(road_t));
- db_parse_wkb_linestring(aRow[2], pNewRoad->pMapPointsArray, &(pNewRoad->rWorldBoundingBox));
-
-#ifdef ENABLE_RIVER_TO_LAKE_LOADTIME_HACK // XXX: combine this and above hack and you get lakes with squiggly edges. whoops. :)
- if(nTypeID == MAP_OBJECT_TYPE_RIVER) {
- mappoint_t* pPointA = &g_array_index(pNewRoad->pMapPointsArray, mappoint_t, 0);
- mappoint_t* pPointB = &g_array_index(pNewRoad->pMapPointsArray, mappoint_t, pNewRoad->pMapPointsArray->len-1);
-
- if(pPointA->fLatitude == pPointB->fLatitude && pPointA->fLongitude == pPointB->fLongitude) {
- nTypeID = MAP_OBJECT_TYPE_LAKE;
- }
- }
-#endif
-
- // Add this item to layer's list of pointstrings
- g_ptr_array_add(pMap->apLayerData[nTypeID]->pRoadsArray, pNewRoad);
- } // end while loop on rows
- //g_print("[%d rows]\n", uRowCount);
- TIMER_SHOW(mytimer, "after rows retrieved");
-
- db_free_result(pResultSet);
- TIMER_SHOW(mytimer, "after free results");
- TIMER_END(mytimer, "END Geometry LOAD");
-
- return TRUE;
- }
- else {
- //g_print(" no rows\n");
- return FALSE;
- }
-}
-
-static gboolean map_data_load_locations(map_t* pMap, maprect_t* pRect)
-{
- g_return_val_if_fail(pMap != NULL, FALSE);
- g_return_val_if_fail(pRect != NULL, FALSE);
-
-// if(map_get_zoomlevel(pMap) < MIN_ZOOM_LEVEL_FOR_LOCATIONS) {
-// return TRUE;
-// }
-
- TIMER_BEGIN(mytimer, "BEGIN Locations LOAD");
-
- // generate SQL
- gchar* pszSQL;
- gchar azCoord1[20], azCoord2[20], azCoord3[20], azCoord4[20], azCoord5[20], azCoord6[20], azCoord7[20], azCoord8[20];
- pszSQL = g_strdup_printf(
- "SELECT Location.ID, Location.LocationSetID, AsBinary(Location.Coordinates), LocationAttributeValue_Name.Value" // LocationAttributeValue_Name.Value is the "Name" field of this Location
- " FROM Location"
- " LEFT JOIN LocationAttributeValue AS LocationAttributeValue_Name ON (LocationAttributeValue_Name.LocationID=Location.ID AND LocationAttributeValue_Name.AttributeNameID=%d)"
- " WHERE"
- " MBRIntersects(GeomFromText('Polygon((%s %s,%s %s,%s %s,%s %s,%s %s))'), Coordinates)",
- LOCATION_ATTRIBUTE_ID_NAME, // attribute ID for 'name'
- // upper left
- g_ascii_dtostr(azCoord1, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord2, 20, pRect->A.fLongitude),
- // upper right
- g_ascii_dtostr(azCoord3, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord4, 20, pRect->B.fLongitude),
- // bottom right
- g_ascii_dtostr(azCoord5, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord6, 20, pRect->B.fLongitude),
- // bottom left
- g_ascii_dtostr(azCoord7, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord8, 20, pRect->A.fLongitude),
- // upper left again
- azCoord1, azCoord2);
- //g_print("sql: %s\n", pszSQL);
-
- db_resultset_t* pResultSet = NULL;
- db_query(pszSQL, &pResultSet);
- g_free(pszSQL);
-
- TIMER_SHOW(mytimer, "after query");
-
- guint32 uRowCount = 0;
- if(pResultSet) {
- db_row_t aRow;
- while((aRow = db_fetch_row(pResultSet))) {
- uRowCount++;
-
- // aRow[0] is ID
- // aRow[1] is LocationSetID
- // aRow[2] is Coordinates in mysql's binary format
- // aRow[3] is Name
-
- // Get layer type that this belongs on
- gint nLocationSetID = atoi(aRow[1]);
-
- // Extract
- location_t* pNewLocation = NULL;
- location_alloc(&pNewLocation);
-
- pNewLocation->nID = atoi(aRow[0]);
-
- // Parse coordinates
- db_parse_wkb_point(aRow[2], &(pNewLocation->Coordinates));
-
- // make a copy of the name field, or "" (never leave it == NULL)
- pNewLocation->pszName = g_strdup(aRow[3] != NULL ? aRow[3] : "");
- map_store_location(pMap, pNewLocation, nLocationSetID);
- } // end while loop on rows
- //g_print("[%d rows]\n", uRowCount);
- TIMER_SHOW(mytimer, "after rows retrieved");
-
- db_free_result(pResultSet);
- TIMER_END(mytimer, "END Locations LOAD");
- return TRUE;
- }
- else {
- TIMER_END(mytimer, "END Locations LOAD (0 results)");
- return FALSE;
- }
-}
-
-static void map_data_clear(map_t* pMap)
-{
- // Clear layers
- gint i,j;
- for(i=0 ; i<G_N_ELEMENTS(pMap->apLayerData) ; i++) {
- maplayer_data_t* pLayerData = pMap->apLayerData[i];
-
- // Free each
- for(j = (pLayerData->pRoadsArray->len - 1) ; j>=0 ; j--) {
- road_t* pRoad = g_ptr_array_remove_index_fast(pLayerData->pRoadsArray, j);
- g_array_free(pRoad->pMapPointsArray, TRUE);
- g_free(pRoad);
- }
- g_assert(pLayerData->pRoadsArray->len == 0);
- }
-
- // Clear locations
- map_init_location_hash(pMap);
-}
-
-double map_get_distance_in_meters(mappoint_t* pA, mappoint_t* pB)
-{
- // XXX: this function is broken.
-
- // This functions calculates the length of the arc of the "greatcircle" that goes through
- // the two points A and B and whos center is the center of the sphere, O.
-
- // When we multiply this angle (in radians) by the radius, we get the length of the arc.
-
- // NOTE: This algorithm wrongly assumes that Earth is a perfect sphere.
- // It is actually slightly egg shaped. But it's good enough.
-
- // All trig functions expect arguments in radians.
- double fLonA_Rad = DEG2RAD(pA->fLongitude);
- double fLonB_Rad = DEG2RAD(pB->fLongitude);
- double fLatA_Rad = DEG2RAD(pA->fLatitude);
- double fLatB_Rad = DEG2RAD(pB->fLatitude);
-
- // Step 1. Calculate AOB (in radians).
- // An explanation of this equation is at http://mathforum.org/library/drmath/view/51722.html
- double fAOB_Rad = acos((cos(fLatA_Rad) * cos(fLatB_Rad) * cos(fLonB_Rad - fLonA_Rad)) + (sin(fLatA_Rad) * sin(fLatB_Rad)));
-
- // Step 2. Multiply the angle by the radius of the sphere to get arc length.
- return fAOB_Rad * RADIUS_OF_WORLD_IN_METERS;
-}
-
-gdouble map_get_straight_line_distance_in_degrees(mappoint_t* p1, mappoint_t* p2)
-{
- gdouble fDeltaX = ((p2->fLongitude) - (p1->fLongitude));
- gdouble fDeltaY = ((p2->fLatitude) - (p1->fLatitude));
-
- return sqrt((fDeltaX*fDeltaX) + (fDeltaY*fDeltaY));
-}
-
-gdouble map_get_distance_in_pixels(map_t* pMap, mappoint_t* p1, mappoint_t* p2)
-{
- rendermetrics_t metrics;
- map_get_render_metrics(pMap, &metrics);
-
- gdouble fX1 = SCALE_X(&metrics, p1->fLongitude);
- gdouble fY1 = SCALE_Y(&metrics, p1->fLatitude);
-
- gdouble fX2 = SCALE_X(&metrics, p2->fLongitude);
- gdouble fY2 = SCALE_Y(&metrics, p2->fLatitude);
-
- gdouble fDeltaX = fX2 - fX1;
- gdouble fDeltaY = fY2 - fY1;
-
- return sqrt((fDeltaX*fDeltaX) + (fDeltaY*fDeltaY));
-}
-
-gboolean map_points_equal(mappoint_t* p1, mappoint_t* p2)
-{
- return( p1->fLatitude == p2->fLatitude && p1->fLongitude == p2->fLongitude);
-}
-
-void map_add_track(map_t* pMap, gint hTrack)
-{
- g_array_append_val(pMap->pTracksArray, hTrack);
-}
-
-// ========================================================
-// Hit Testing
-// ========================================================
-
-void map_hitstruct_free(map_t* pMap, maphit_t* pHitStruct)
-{
- if(pHitStruct == NULL) return;
-
- // free type-specific stuff
- if(pHitStruct->eHitType == MAP_HITTYPE_URL) {
- g_free(pHitStruct->URLHit.pszURL);
- }
-
- // free common stuff
- g_free(pHitStruct->pszText);
- g_free(pHitStruct);
-}
-
-// XXX: perhaps make map_hit_test return a more complex structure indicating what type of hit it is?
-gboolean map_hit_test(map_t* pMap, mappoint_t* pMapPoint, maphit_t** ppReturnStruct)
+gboolean map_object_type_atoi(const gchar* pszName, gint* pnReturnObjectTypeID)
{
-#if 0 // GGGGGGGGGGGGGGGG
- rendermetrics_t rendermetrics;
- map_get_render_metrics(pMap, &rendermetrics);
-
- if(map_hit_test_locationselections(pMap, &rendermetrics, pMap->pLocationSelectionArray, pMapPoint, ppReturnStruct)) {
- return TRUE;
- }
-
- if(map_hit_test_locationsets(pMap, &rendermetrics, pMapPoint, ppReturnStruct)) {
- return TRUE;
- }
-
- // Test things in the REVERSE order they are drawn (otherwise we'll match things that have been painted-over)
gint i;
- for(i=G_N_ELEMENTS(layerdraworder)-1 ; i>=0 ; i--) {
- if(layerdraworder[i].eSubLayerRenderType != SUBLAYER_RENDERTYPE_LINES) continue;
-
- gint nLayer = layerdraworder[i].nLayer;
-
- // use width from whichever layer it's wider in
- gdouble fLineWidth = max(g_aLayers[nLayer]->Style.aSubLayers[0].afLineWidths[pMap->uZoomLevel-1],
- g_aLayers[nLayer]->Style.aSubLayers[1].afLineWidths[pMap->uZoomLevel-1]);
-
-#define EXTRA_CLICKABLE_ROAD_IN_PIXELS (3)
-
- // make thin roads a little easier to hit
- // fLineWidth = max(fLineWidth, MIN_ROAD_HIT_TARGET_WIDTH);
-
- // XXX: hack, map_pixels should really take a floating point instead.
- gdouble fMaxDistance = map_pixels_to_degrees(pMap, 1, pMap->uZoomLevel) * ((fLineWidth/2) + EXTRA_CLICKABLE_ROAD_IN_PIXELS); // half width on each side
-
- if(map_hit_test_layer_roads(pMap->apLayerData[nLayer]->pRoadsArray, fMaxDistance, pMapPoint, ppReturnStruct)) {
- return TRUE;
- }
- // otherwise try next layer...
- }
-#endif
- return FALSE;
-}
-
-static gboolean map_hit_test_layer_roads(GPtrArray* pRoadsArray, gdouble fMaxDistance, mappoint_t* pHitPoint, maphit_t** ppReturnStruct)
-{
- g_assert(ppReturnStruct != NULL);
- g_assert(*ppReturnStruct == NULL); // pointer to null pointer
-
- /* this is helpful for testing with the g_print()s in map_hit_test_line() */
-/* mappoint_t p1 = {2,2}; */
-/* mappoint_t p2 = {-10,10}; */
-/* mappoint_t p3 = {0,10}; */
-/* map_hit_test_line(&p1, &p2, &p3, 20); */
-/* return FALSE; */
-
- // Loop through line strings, order doesn't matter here since they're all on the same level.
- gint iString;
- for(iString=0 ; iString<pRoadsArray->len ; iString++) {
- road_t* pRoad = g_ptr_array_index(pRoadsArray, iString);
- if(pRoad->pMapPointsArray->len < 2) continue;
-
- // start on 1 so we can do -1 trick below
- gint iPoint;
- for(iPoint=1 ; iPoint<pRoad->pMapPointsArray->len ; iPoint++) {
- mappoint_t* pPoint1 = &g_array_index(pRoad->pMapPointsArray, mappoint_t, iPoint-1);
- mappoint_t* pPoint2 = &g_array_index(pRoad->pMapPointsArray, mappoint_t, iPoint);
-
- mappoint_t pointClosest;
- gdouble fPercentAlongLine;
-
- // hit test this line
- if(map_hit_test_line(pPoint1, pPoint2, pHitPoint, fMaxDistance, &pointClosest, &fPercentAlongLine)) {
- //g_print("fPercentAlongLine = %f\n",fPercentAlongLine);
-
- // fill out a new maphit_t struct with details
- maphit_t* pHitStruct = g_new0(maphit_t, 1);
- pHitStruct->eHitType = MAP_HITTYPE_ROAD;
-
- if(pRoad->pszName[0] == '\0') {
- pHitStruct->pszText = g_strdup("<i>unnamed</i>");
- }
- else {
- ESide eSide = map_side_test_line(pPoint1, pPoint2, &pointClosest, pHitPoint);
-
- gint nAddressStart;
- gint nAddressEnd;
- if(eSide == SIDE_LEFT) {
- nAddressStart = pRoad->nAddressLeftStart;
- nAddressEnd = pRoad->nAddressLeftEnd;
- }
- else {
- nAddressStart = pRoad->nAddressRightStart;
- nAddressEnd = pRoad->nAddressRightEnd;
- }
-
- if(nAddressStart == 0 || nAddressEnd == 0) {
- pHitStruct->pszText = g_strdup_printf("%s", pRoad->pszName);
- }
- else {
- gint nMinAddres = min(nAddressStart, nAddressEnd);
- gint nMaxAddres = max(nAddressStart, nAddressEnd);
-
- pHitStruct->pszText = g_strdup_printf("%s <b>#%d-%d</b>", pRoad->pszName, nMinAddres, nMaxAddres);
- }
- }
- *ppReturnStruct = pHitStruct;
- return TRUE;
- }
- }
- }
- return FALSE;
-}
-
-// Does the given point come close enough to the line segment to be considered a hit?
-static gboolean map_hit_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pHitPoint, gdouble fMaxDistance, mappoint_t* pReturnClosestPoint, gdouble* pfReturnPercentAlongLine)
-{
- if(pHitPoint->fLatitude < (pPoint1->fLatitude - fMaxDistance) && pHitPoint->fLatitude < (pPoint2->fLatitude - fMaxDistance)) return FALSE;
- if(pHitPoint->fLongitude < (pPoint1->fLongitude - fMaxDistance) && pHitPoint->fLongitude < (pPoint2->fLongitude - fMaxDistance)) return FALSE;
-
- // Some bad ASCII art demonstrating the situation:
- //
- // / (u)
- // / |
- // / |
- // (0,0) =====(a)========== (v)
-
- // v is the translated-to-origin vector of line (road)
- // u is the translated-to-origin vector of the hitpoint
- // a is the closest point on v to the end of u (the hit point)
-
- //
- // 1. Convert p1->p2 vector into a vector (v) that is assumed to come out of the origin (0,0)
- //
- mappoint_t v;
- v.fLatitude = pPoint2->fLatitude - pPoint1->fLatitude; // 10->90 becomes 0->80 (just store 80)
- v.fLongitude = pPoint2->fLongitude - pPoint1->fLongitude;
-
- gdouble fLengthV = sqrt((v.fLatitude*v.fLatitude) + (v.fLongitude*v.fLongitude));
- if(fLengthV == 0.0) return FALSE; // bad data: a line segment with no length?
-
- //
- // 2. Make a unit vector out of v (meaning same direction but length=1) by dividing v by v's length
- //
- mappoint_t unitv;
- unitv.fLatitude = v.fLatitude / fLengthV;
- unitv.fLongitude = v.fLongitude / fLengthV; // unitv is now a unit (=1.0) length v
-
- //
- // 3. Translate the hitpoint in the same way we translated v
- //
- mappoint_t u;
- u.fLatitude = pHitPoint->fLatitude - pPoint1->fLatitude;
- u.fLongitude = pHitPoint->fLongitude - pPoint1->fLongitude;
-
- //
- // 4. Use the dot product of (unitv) and (u) to find (a), the point along (v) that is closest to (u). see diagram above.
- //
- gdouble fLengthAlongV = (unitv.fLatitude * u.fLatitude) + (unitv.fLongitude * u.fLongitude);
-
- // Does it fall along the length of the line *segment* v? (we know it falls along the infinite line v, but that does us no good.)
- // (This produces false negatives on round/butt end caps, but that's better that a false positive when another line is actually there!)
- if(fLengthAlongV > 0 && fLengthAlongV < fLengthV) {
- mappoint_t a;
- a.fLatitude = v.fLatitude * (fLengthAlongV / fLengthV); // multiply each component by the percentage
- a.fLongitude = v.fLongitude * (fLengthAlongV / fLengthV);
- // NOTE: (a) is *not* where it actually hit on the *map*. don't draw this point! we'd have to translate it back away from the origin.
-
- //
- // 5. Calculate the distance from the end of (u) to (a). If it's less than the fMaxDistance, it's a hit.
- //
- gdouble fRise = u.fLatitude - a.fLatitude;
- gdouble fRun = u.fLongitude - a.fLongitude;
- gdouble fDistanceSquared = fRise*fRise + fRun*fRun; // compare squared distances. same results but without the sqrt.
-
- if(fDistanceSquared <= (fMaxDistance*fMaxDistance)) {
- /* debug aids */
- /* g_print("pPoint1 (%f,%f)\n", pPoint1->fLatitude, pPoint1->fLongitude); */
- /* g_print("pPoint2 (%f,%f)\n", pPoint2->fLatitude, pPoint2->fLongitude); */
- /* g_print("pHitPoint (%f,%f)\n", pHitPoint->fLatitude, pHitPoint->fLongitude); */
- /* g_print("v (%f,%f)\n", v.fLatitude, v.fLongitude); */
- /* g_print("u (%f,%f)\n", u.fLatitude, u.fLongitude); */
- /* g_print("unitv (%f,%f)\n", unitv.fLatitude, unitv.fLongitude); */
- /* g_print("fDotProduct = %f\n", fDotProduct); */
- /* g_print("a (%f,%f)\n", a.fLatitude, a.fLongitude); */
- /* g_print("fDistance = %f\n", sqrt(fDistanceSquared)); */
- if(pReturnClosestPoint) {
- pReturnClosestPoint->fLatitude = a.fLatitude + pPoint1->fLatitude;
- pReturnClosestPoint->fLongitude = a.fLongitude + pPoint1->fLongitude;
- }
-
- if(pfReturnPercentAlongLine) {
- *pfReturnPercentAlongLine = (fLengthAlongV / fLengthV);
- }
+ for(i=0 ; i<MAP_NUM_OBJECT_TYPES ; i++) {
+ if(strcmp(pszName, g_apszMapObjectTypeNames[i]) == 0) {
+ *pnReturnObjectTypeID = i;
return TRUE;
}
}
return FALSE;
}
-static ESide map_side_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pClosestPointOnLine, mappoint_t* pHitPoint)
-{
- // make a translated-to-origin *perpendicular* vector of the line (points to the "left" of the line when walking from point 1 to 2)
- mappoint_t v;
- v.fLatitude = (pPoint2->fLongitude - pPoint1->fLongitude); // NOTE: swapping lat and lon to make perpendicular
- v.fLongitude = -(pPoint2->fLatitude - pPoint1->fLatitude);
-
- // make a translated-to-origin vector of the line from closest point to hitpoint
- mappoint_t u;
- u.fLatitude = pHitPoint->fLatitude - pClosestPointOnLine->fLatitude;
- u.fLongitude = pHitPoint->fLongitude - pClosestPointOnLine->fLongitude;
-
- // figure out the signs of the elements of the vectors
- gboolean bULatPositive = (u.fLatitude >= 0);
- gboolean bULonPositive = (u.fLongitude >= 0);
- gboolean bVLatPositive = (v.fLatitude >= 0);
- gboolean bVLonPositive = (v.fLongitude >= 0);
-
- //g_print("%s,%s %s,%s\n", bVLonPositive?"Y":"N", bVLatPositive?"Y":"N", bULonPositive?"Y":"N", bULatPositive?"Y":"N");
-
- // if the signs agree, it's to the left, otherwise to the right
- if(bULatPositive == bVLatPositive && bULonPositive == bVLonPositive) {
- return SIDE_LEFT;
- }
- else {
- // let's check our algorithm: if the signs aren't both the same, they should be both opposite
- // ...unless the vector from hitpoint to line center is 0, which doesn't have sign
-
- //g_print("%f,%f %f,%f\n", u.fLatitude, u.fLongitude, v.fLatitude, v.fLongitude);
- g_assert(bULatPositive != bVLatPositive || u.fLatitude == 0.0);
- g_assert(bULonPositive != bVLonPositive || u.fLongitude == 0.0);
- return SIDE_RIGHT;
- }
-}
-
-// hit test all locations
-static gboolean map_hit_test_locationsets(map_t* pMap, rendermetrics_t* pRenderMetrics, mappoint_t* pHitPoint, maphit_t** ppReturnStruct)
+gboolean map_layer_render_type_atoi(const gchar* pszName, gint* pnReturnRenderTypeID)
{
- gdouble fMaxDistance = map_pixels_to_degrees(pMap, 1, pMap->uZoomLevel) * 3; // XXX: don't hardcode distance :)
+ gchar* g_apszMapRenderTypeNames[] = {"lines", "polygons", "line-labels", "polygon-labels", "fill", "locations", "location-labels"};
- const GPtrArray* pLocationSetsArray = locationset_get_array();
gint i;
- for(i=0 ; i<pLocationSetsArray->len ; i++) {
- locationset_t* pLocationSet = g_ptr_array_index(pLocationSetsArray, i);
-
- // the user is NOT trying to click on invisible things :)
- if(!locationset_is_visible(pLocationSet)) continue;
-
- // 2. Get array of Locations from the hash table using LocationSetID
- GPtrArray* pLocationsArray;
- pLocationsArray = g_hash_table_lookup(pMap->pLocationArrayHashTable, &(pLocationSet->nID));
- if(pLocationsArray != NULL) {
- if(map_hit_test_locations(pMap, pRenderMetrics, pLocationsArray, pHitPoint, ppReturnStruct)) {
- return TRUE;
- }
- }
- else {
- // none loaded
- }
- }
- return FALSE;
-}
-
-// hit-test an array of locations
-static gboolean map_hit_test_locations(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationsArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct)
-{
- gint i;
- for(i=(pLocationsArray->len-1) ; i>=0 ; i--) { // NOTE: test in *reverse* order so we hit the ones drawn on top first
- location_t* pLocation = g_ptr_array_index(pLocationsArray, i);
-
- // bounding box test
- if(pLocation->Coordinates.fLatitude < pRenderMetrics->rWorldBoundingBox.A.fLatitude
- || pLocation->Coordinates.fLongitude < pRenderMetrics->rWorldBoundingBox.A.fLongitude
- || pLocation->Coordinates.fLatitude > pRenderMetrics->rWorldBoundingBox.B.fLatitude
- || pLocation->Coordinates.fLongitude > pRenderMetrics->rWorldBoundingBox.B.fLongitude)
- {
- continue; // not visible
- }
-
- gdouble fX1 = SCALE_X(pRenderMetrics, pLocation->Coordinates.fLongitude);
- gdouble fY1 = SCALE_Y(pRenderMetrics, pLocation->Coordinates.fLatitude);
- gdouble fX2 = SCALE_X(pRenderMetrics, pHitPoint->fLongitude);
- gdouble fY2 = SCALE_Y(pRenderMetrics, pHitPoint->fLatitude);
-
- gdouble fDeltaX = fX2 - fX1;
- gdouble fDeltaY = fY2 - fY1;
-
- fDeltaX = abs(fDeltaX);
- fDeltaY = abs(fDeltaY);
-
- if(fDeltaX <= 3 && fDeltaY <= 3) {
- // fill out a new maphit_t struct with details
- maphit_t* pHitStruct = g_new0(maphit_t, 1);
- pHitStruct->eHitType = MAP_HITTYPE_LOCATION;
- pHitStruct->LocationHit.nLocationID = pLocation->nID;
-
- pHitStruct->LocationHit.Coordinates.fLatitude = pLocation->Coordinates.fLatitude;
- pHitStruct->LocationHit.Coordinates.fLongitude = pLocation->Coordinates.fLongitude;
-
- if(pLocation->pszName[0] == '\0') {
- pHitStruct->pszText = g_strdup_printf("<i>unnamed POI %d</i>", pLocation->nID);
- }
- else {
- pHitStruct->pszText = g_strdup(pLocation->pszName);
- }
- *ppReturnStruct = pHitStruct;
+ for(i=0 ; i<G_N_ELEMENTS(g_apszMapRenderTypeNames) ; i++) {
+ if(strcmp(pszName, g_apszMapRenderTypeNames[i]) == 0) {
+ *pnReturnRenderTypeID = i;
return TRUE;
}
}
return FALSE;
}
-gboolean hit_test_screenpoint_in_rect(screenpoint_t* pPt, screenrect_t* pRect)
-{
- return(pPt->nX >= pRect->A.nX && pPt->nX <= pRect->B.nX && pPt->nY >= pRect->A.nY && pPt->nY <= pRect->B.nY);
-}
-
-static gboolean map_hit_test_locationselections(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationSelectionArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct)
-{
- screenpoint_t screenpoint;
- screenpoint.nX = (gint)SCALE_X(pRenderMetrics, pHitPoint->fLongitude);
- screenpoint.nY = (gint)SCALE_Y(pRenderMetrics, pHitPoint->fLatitude);
-
- gint i;
- for(i=(pLocationSelectionArray->len-1) ; i>=0 ; i--) {
- locationselection_t* pLocationSelection = g_ptr_array_index(pLocationSelectionArray, i);
-
- if(pLocationSelection->bVisible == FALSE) continue;
-
- if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->InfoBoxRect))) {
- // fill out a new maphit_t struct with details
- maphit_t* pHitStruct = g_new0(maphit_t, 1);
-
- if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->InfoBoxCloseRect))) {
- pHitStruct->eHitType = MAP_HITTYPE_LOCATIONSELECTION_CLOSE;
- pHitStruct->pszText = g_strdup("close");
- pHitStruct->LocationSelectionHit.nLocationID = pLocationSelection->nLocationID;
- }
- else if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->EditRect))) {
- pHitStruct->eHitType = MAP_HITTYPE_LOCATIONSELECTION_EDIT;
- pHitStruct->pszText = g_strdup("edit");
- pHitStruct->LocationSelectionHit.nLocationID = pLocationSelection->nLocationID;
- }
- else {
- gboolean bURLMatch = FALSE;
-
- gint iURL;
- for(iURL=0 ; iURL<pLocationSelection->nNumURLs ; iURL++) {
- if(hit_test_screenpoint_in_rect(&screenpoint, &(pLocationSelection->aURLs[iURL].Rect))) {
- pHitStruct->eHitType = MAP_HITTYPE_URL;
- pHitStruct->pszText = g_strdup("click to open location");
- pHitStruct->URLHit.pszURL = g_strdup(pLocationSelection->aURLs[iURL].pszURL);
-
- bURLMatch = TRUE;
- break;
- }
- }
-
- // no url match, just return a generic "hit the locationselection box"
- if(!bURLMatch) {
- pHitStruct->eHitType = MAP_HITTYPE_LOCATIONSELECTION;
- pHitStruct->pszText = g_strdup("");
- pHitStruct->LocationSelectionHit.nLocationID = pLocationSelection->nLocationID;
- }
- }
- *ppReturnStruct = pHitStruct;
- return TRUE;
- }
- }
- return FALSE;
-}
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
+//
// Get an attribute from a selected location
@@ -1226,32 +481,6 @@ const gchar* map_location_selection_get_attribute(const map_t* pMap, const locat
return NULL;
}
-gboolean map_object_type_atoi(const gchar* pszName, gint* pnReturnObjectTypeID)
-{
- gint i;
- for(i=0 ; i<MAP_NUM_OBJECT_TYPES ; i++) {
- if(strcmp(pszName, g_apszMapObjectTypeNames[i]) == 0) {
- *pnReturnObjectTypeID = i;
- return TRUE;
- }
- }
- return FALSE;
-}
-
-gboolean map_layer_render_type_atoi(const gchar* pszName, gint* pnReturnRenderTypeID)
-{
- gchar* g_apszMapRenderTypeNames[] = {"lines", "polygons", "line-labels", "polygon-labels", "fill", "locations", "location-labels"};
-
- gint i;
- for(i=0 ; i<G_N_ELEMENTS(g_apszMapRenderTypeNames) ; i++) {
- if(strcmp(pszName, g_apszMapRenderTypeNames[i]) == 0) {
- *pnReturnRenderTypeID = i;
- return TRUE;
- }
- }
- return FALSE;
-}
-
void map_callback_free_locations_array(gpointer p)
{
GPtrArray* pLocationsArray = (GPtrArray*)p;
@@ -1364,12 +593,63 @@ gboolean map_location_selection_remove(map_t* pMap, gint nLocationID)
return FALSE; // removed
}
-gboolean map_rects_overlap(const maprect_t* p1, const maprect_t* p2)
+
+#ifdef ROADSTER_DEAD_CODE
+/*
+static gboolean map_enhance_linestring(GPtrArray* pSourceArray, GPtrArray* pDestArray, gboolean (*callback_alloc_point)(mappoint_t**), gdouble fMaxDistanceBetweenPoints, gdouble fMaxRandomDistance)
{
- if(p1->B.fLatitude < p2->A.fLatitude) return FALSE;
- if(p1->B.fLongitude < p2->A.fLongitude) return FALSE;
- if(p1->A.fLatitude > p2->B.fLatitude) return FALSE;
- if(p1->A.fLongitude > p2->B.fLongitude) return FALSE;
+ g_assert(pSourceArray->len >= 2);
+//g_print("pSourceArray->len = %d\n", pSourceArray->len);
- return TRUE;
+ // add first point
+ g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, 0));
+
+ gint i = 0;
+ for(i=0 ; i<(pSourceArray->len-1) ; i++) {
+ mappoint_t* pPoint1 = g_ptr_array_index(pSourceArray, i);
+ mappoint_t* pPoint2 = g_ptr_array_index(pSourceArray, i+1);
+
+ gdouble fRise = (pPoint2->fLatitude - pPoint1->fLatitude);
+ gdouble fRun = (pPoint2->fLongitude - pPoint1->fLongitude);
+
+ gdouble fLineLength = sqrt((fRun*fRun) + (fRise*fRise));
+// g_print("fLineLength = %f\n", fLineLength);
+ gint nNumMiddlePoints = (gint)(fLineLength / fMaxDistanceBetweenPoints);
+// g_print("(fLineLength / fMaxDistanceBetweenPoints) = nNumNewPoints; %f / %f = %d\n", fLineLength, fMaxDistanceBetweenPoints, nNumNewPoints);
+ if(nNumMiddlePoints == 0) continue; // nothing to add
+
+// g_print("fDistanceBetweenPoints = %f\n", fDistanceBetweenPoints);
+
+ gdouble fNormalizedX = fRun / fLineLength;
+ gdouble fNormalizedY = fRise / fLineLength;
+// g_print("fNormalizedX = %f\n", fNormalizedX);
+// g_print("fNormalizedY = %f\n", fNormalizedY);
+
+ gdouble fPerpendicularNormalizedX = fRise / fLineLength;
+ gdouble fPerpendicularNormalizedY = -(fRun / fLineLength);
+
+ // add points along the line
+ gdouble fDistanceBetweenPoints = fLineLength / (gdouble)(nNumMiddlePoints + 1);
+
+ gint j;
+ for(j=0 ; j<nNumMiddlePoints ; j++) {
+ mappoint_t* pNewPoint = NULL;
+ callback_alloc_point(&pNewPoint);
+ gdouble fDistanceFromPoint1 = (j+1) * fDistanceBetweenPoints;
+
+ pNewPoint->fLongitude = pPoint1->fLongitude + (fDistanceFromPoint1 * fNormalizedX);
+ pNewPoint->fLatitude = pPoint1->fLatitude + (fDistanceFromPoint1 * fNormalizedY);
+
+ gdouble fRandomMovementLength = fMaxRandomDistance * g_random_double_range(-1.0, 1.0);
+ pNewPoint->fLongitude += (fPerpendicularNormalizedX * fRandomMovementLength); // move each component
+ pNewPoint->fLatitude += (fPerpendicularNormalizedY * fRandomMovementLength);
+
+ g_ptr_array_add(pDestArray, pNewPoint);
+ }
+ }
+ // add last point
+ g_ptr_array_add(pDestArray, g_ptr_array_index(pSourceArray, pSourceArray->len-1));
+//g_print("pDestArray->len = %d\n", pDestArray->len);
}
+*/
+#endif
diff --git a/src/map.h b/src/map.h
index b526e31..6c288d2 100644
--- a/src/map.h
+++ b/src/map.h
@@ -24,6 +24,7 @@
#ifndef _MAP_H_
#define _MAP_H_
+#include <math.h>
#include "gfreelist.h"
//
@@ -48,6 +49,9 @@
#define MAP_OBJECT_TYPE_FIRST (1)
#define MAP_OBJECT_TYPE_LAST (12)
+#define MAP_LEVEL_OF_DETAIL_BEST (0)
+#define MAP_LEVEL_OF_DETAIL_WORST (3)
+#define MAP_NUM_LEVELS_OF_DETAIL (4)
//
// Line CAP styles
@@ -101,9 +105,9 @@ typedef enum {
struct GtkWidget;
#define MIN_ZOOM_LEVEL (1)
-#define MAX_ZOOM_LEVEL (5)
-#define NUM_ZOOM_LEVELS (5)
-#define MIN_ZOOM_LEVEL_FOR_LOCATIONS (6) // don't show POI above this level
+#define MAX_ZOOM_LEVEL (41)
+#define NUM_ZOOM_LEVELS (41)
+#define MIN_ZOOM_LEVEL_FOR_LOCATIONS (25) // don't show POI above this level
#include "scenemanager.h"
@@ -153,7 +157,10 @@ typedef struct {
EDistanceUnits eScaleMetricUnit;
gint nScaleMetricNumber;
- gchar* szName;
+ //gchar* szName;
+
+ gint nStyleZoomLevel; // pretend we're zoomlevel X because there are more real zoomlevels than in the map layer style definitions
+ gint nLevelOfDetail; // pretend we're zoomlevel X because there are more real zoomlevels than in the map layer style definitions
} zoomlevel_t;
extern zoomlevel_t g_sZoomLevels[];
@@ -173,19 +180,22 @@ typedef struct {
maprect_t rWorldBoundingBox;
gint nWindowWidth;
gint nWindowHeight;
+ gint nLevelOfDetail;
} rendermetrics_t;
#define SCALE_X(p, x) ((((x) - (p)->rWorldBoundingBox.A.fLongitude) / (p)->fScreenLongitude) * (p)->nWindowWidth)
#define SCALE_Y(p, y) ((p)->nWindowHeight - ((((y) - (p)->rWorldBoundingBox.A.fLatitude) / (p)->fScreenLatitude) * (p)->nWindowHeight))
-typedef struct {
- GPtrArray* pRoadsArray;
-} maplayer_data_t;
+// typedef struct {
+// GPtrArray* pRoadsArray;
+// } maplayer_data_t;
typedef struct {
GPtrArray* pLocationsArray;
} maplayer_locations_t;
+#include "map_tilemanager.h"
+
typedef struct {
mappoint_t MapCenter;
dimensions_t MapDimensions;
@@ -195,7 +205,9 @@ typedef struct {
// data
GArray *pTracksArray;
- maplayer_data_t *apLayerData[ MAP_NUM_OBJECT_TYPES + 1 ];
+// maplayer_data_t *apLayerData[ MAP_NUM_OBJECT_TYPES + 1 ];
+
+ maptilemanager_t* pTileManager;
// Locationsets
GHashTable *pLocationArrayHashTable;
@@ -209,42 +221,6 @@ typedef struct {
} map_t;
typedef enum {
- MAP_HITTYPE_LOCATION,
- MAP_HITTYPE_ROAD,
-
- // the following all use LocationSelectionHit in the union below
- MAP_HITTYPE_LOCATIONSELECTION, // hit somewhere on a locationselection graphic (info balloon)
- MAP_HITTYPE_LOCATIONSELECTION_CLOSE, // hit locationselection graphic close graphic (info balloon [X])
- MAP_HITTYPE_LOCATIONSELECTION_EDIT, // hit locationselection graphic edit graphic (info balloon "edit")
-
- MAP_HITTYPE_URL,
-} EMapHitType;
-
-typedef struct {
- EMapHitType eHitType;
- gchar* pszText;
- union {
- struct {
- gint nLocationID;
- mappoint_t Coordinates;
- } LocationHit;
-
- struct {
- gint nRoadID;
- mappoint_t ClosestPoint;
- } RoadHit;
-
- struct {
- gint nLocationID;
- } LocationSelectionHit;
-
- struct {
- gchar* pszURL;
- } URLHit;
- };
-} maphit_t;
-
-typedef enum {
MAP_LAYER_RENDERTYPE_LINES,
MAP_LAYER_RENDERTYPE_POLYGONS,
MAP_LAYER_RENDERTYPE_LINE_LABELS,
@@ -287,8 +263,9 @@ typedef struct {
#define DRAWFLAG_ALL (1|2)
-#define NUM_SUBLAYER_TO_DRAW (21) //(24)
-extern draworder_t layerdraworder[NUM_SUBLAYER_TO_DRAW]; //
+// #define NUM_SUBLAYER_TO_DRAW (21) //(24)
+// extern draworder_t layerdraworder[NUM_SUBLAYER_TO_DRAW]; //
+
void map_init(void);
gboolean map_new(map_t** ppMap, GtkWidget* pTargetWidget);
@@ -331,9 +308,6 @@ void map_release_pixmap(map_t* pMap);
void map_draw(map_t* pMap, GdkPixmap* pTargetPixmap, gint nDrawFlags);
void map_add_track(map_t* pMap, gint hTrack);
-gboolean map_hit_test(map_t* pMap, mappoint_t* pMapPoint, maphit_t** ppReturnStruct);
-void map_hitstruct_free(map_t* pMap, maphit_t* pHitStruct);
-
gboolean map_location_selection_add(map_t* pMap, gint nLocationID);
gboolean map_location_selection_remove(map_t* pMap, gint nLocationID);
@@ -344,4 +318,9 @@ gboolean map_layer_render_type_atoi(const gchar* pszName, gint* pnReturnRenderTy
gboolean map_rects_overlap(const maprect_t* p1, const maprect_t* p2);
+void map_zoom_to_screenrect(map_t* pMap, const screenrect_t* pRect);
+gint map_screenrect_width(const screenrect_t* pRect);
+gint map_screenrect_height(const screenrect_t* pRect);
+void map_get_screenrect_centerpoint(const screenrect_t* pRect, screenpoint_t* pPoint);
+
#endif
diff --git a/src/map_draw_cairo.c b/src/map_draw_cairo.c
index e3543e1..6d65ce9 100644
--- a/src/map_draw_cairo.c
+++ b/src/map_draw_cairo.c
@@ -99,7 +99,7 @@ void map_draw_cairo_set_rgba(cairo_t* pCairo, color_t* pColor)
cairo_set_source_rgba(pCairo, pColor->fRed, pColor->fGreen, pColor->fBlue, pColor->fAlpha);
}
-void map_draw_cairo(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags)
+void map_draw_cairo(map_t* pMap, GPtrArray* pTiles, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags)
{
// 1. Set draw target to X Drawable
Display* dpy;
@@ -143,32 +143,38 @@ void map_draw_cairo(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPi
for(i=pMap->pLayersArray->len-1 ; i>=0 ; i--) {
maplayer_t* pLayer = g_ptr_array_index(pMap->pLayersArray, i);
+ gint nStyleZoomLevel = g_sZoomLevels[pRenderMetrics->nZoomLevel-1].nStyleZoomLevel;
+
if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_LINES) {
if(nDrawFlags & DRAWFLAG_GEOMETRY) {
- map_draw_cairo_layer_roads(pMap, pCairo, pRenderMetrics,
- pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]);
+// map_draw_cairo_layer_roads(pMap, pCairo, pRenderMetrics,
+// pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
+// pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]);
}
}
else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_POLYGONS) {
if(nDrawFlags & DRAWFLAG_GEOMETRY) {
- map_draw_cairo_layer_polygons(pMap, pCairo, pRenderMetrics,
- pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]);
+// map_draw_cairo_layer_polygons(pMap, pCairo, pRenderMetrics,
+// pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
+// pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]);
}
}
else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_LINE_LABELS) {
if(nDrawFlags & DRAWFLAG_LABELS) {
- map_draw_cairo_layer_road_labels(pMap, pCairo, pRenderMetrics,
- pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]);
+ gint iTile;
+ for(iTile=0 ; iTile < pTiles->len ; iTile++) {
+ maptile_t* pTile = g_ptr_array_index(pTiles, iTile);
+ map_draw_cairo_layer_road_labels(pMap, pCairo, pRenderMetrics,
+ pTile->apMapObjectArrays[pLayer->nDataSource], // data
+ pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]);
+ }
}
}
else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_POLYGON_LABELS) {
if(nDrawFlags & DRAWFLAG_LABELS) {
- map_draw_cairo_layer_polygon_labels(pMap, pCairo, pRenderMetrics,
- pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]);
+// map_draw_cairo_layer_polygon_labels(pMap, pCairo, pRenderMetrics,
+// pMap->apLayerData[pLayer->nDataSource]->pRoadsArray,
+// pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]);
}
}
}
@@ -246,6 +252,11 @@ void map_draw_cairo_layer_road_labels(map_t* pMap, cairo_t* pCairo, rendermetric
for(i=0 ; i<pRoadsArray->len ; i++) {
road_t* pRoad = g_ptr_array_index(pRoadsArray, i);
+
+ if(!map_rects_overlap(&(pRoad->rWorldBoundingBox), &(pRenderMetrics->rWorldBoundingBox))) {
+ continue;
+ }
+
if(pRoad->pszName[0] != '\0') {
map_draw_cairo_road_label(pMap, pCairo, pLayerStyle, pRenderMetrics, pRoad->pMapPointsArray, pRoad->pszName);
}
@@ -480,26 +491,11 @@ static void map_draw_cairo_road_label_one_segment(map_t* pMap, cairo_t *pCairo,
mappoint_t* pTmp = pMapPoint1; pMapPoint1 = pMapPoint2; pMapPoint2 = pTmp;
}
- // find extents
- gdouble fMaxLat = max(pMapPoint1->fLatitude, pMapPoint2->fLatitude);
- gdouble fMinLat = min(pMapPoint1->fLatitude, pMapPoint2->fLatitude);
- gdouble fMaxLon = max(pMapPoint1->fLongitude, pMapPoint2->fLongitude);
- gdouble fMinLon = min(pMapPoint1->fLongitude, pMapPoint2->fLongitude);
-
gdouble fX1 = SCALE_X(pRenderMetrics, pMapPoint1->fLongitude);
gdouble fY1 = SCALE_Y(pRenderMetrics, pMapPoint1->fLatitude);
gdouble fX2 = SCALE_X(pRenderMetrics, pMapPoint2->fLongitude);
gdouble fY2 = SCALE_Y(pRenderMetrics, pMapPoint2->fLatitude);
- // rectangle overlap test
- if(fMaxLat < pRenderMetrics->rWorldBoundingBox.A.fLatitude
- || fMaxLon < pRenderMetrics->rWorldBoundingBox.A.fLongitude
- || fMinLat > pRenderMetrics->rWorldBoundingBox.B.fLatitude
- || fMinLon > pRenderMetrics->rWorldBoundingBox.B.fLongitude)
- {
- return;
- }
-
gdouble fRise = fY2 - fY1;
gdouble fRun = fX2 - fX1;
gdouble fLineLengthSquared = (fRun*fRun) + (fRise*fRise);
@@ -1276,7 +1272,7 @@ void map_draw_cairo_polygon_label(map_t* pMap, cairo_t *pCairo, maplayerstyle_t*
static void map_draw_cairo_map_scale(map_t* pMap, cairo_t *pCairo, rendermetrics_t* pRenderMetrics)
{
- zoomlevel_t* pZoomLevel = &g_sZoomLevels[pRenderMetrics->nZoomLevel];
+ zoomlevel_t* pZoomLevel = &g_sZoomLevels[pRenderMetrics->nZoomLevel-1];
// Imperial
gdouble fImperialLength = 0;
diff --git a/src/map_draw_cairo.h b/src/map_draw_cairo.h
index 6e1f3d9..441d0fc 100644
--- a/src/map_draw_cairo.h
+++ b/src/map_draw_cairo.h
@@ -26,6 +26,6 @@
#include <cairo.h>
-void map_draw_cairo(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags);
+void map_draw_cairo(map_t* pMap, GPtrArray* pTiles, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags);
#endif
diff --git a/src/map_draw_gdk.c b/src/map_draw_gdk.c
index 4f53b6d..dd455bb 100644
--- a/src/map_draw_gdk.c
+++ b/src/map_draw_gdk.c
@@ -1,5 +1,5 @@
/***************************************************************************
- * map_draw_cairo.c
+ * map_draw_gdk.c
*
* Copyright 2005 Ian McIntosh
* ian_mcintosh@linuxadvocate.org
@@ -69,7 +69,24 @@ void map_draw_gdk_set_color(GdkGC* pGC, color_t* pColor)
gdk_gc_set_rgb_fg_color(pGC, &clr);
}
-void map_draw_gdk(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags)
+void map_draw_gdk_xor_rect(map_t* pMap, GdkDrawable* pTargetDrawable, screenrect_t* pRect)
+{
+ GdkGC* pGC = pMap->pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->pTargetWidget)];
+
+ GdkGCValues gcValues;
+ gdk_gc_get_values(pGC, &gcValues);
+
+ GdkColor clrWhite = {0, 32000, 32000, 32000};
+ gdk_gc_set_function(pGC, GDK_XOR);
+ gdk_gc_set_rgb_fg_color(pGC, &clrWhite);
+ gdk_draw_rectangle(pTargetDrawable, pGC, FALSE,
+ min(pRect->A.nX, pRect->B.nX), min(pRect->A.nY, pRect->B.nY), // x,y
+ map_screenrect_width(pRect), map_screenrect_height(pRect)); // w,h
+
+ gdk_gc_set_values(pGC, &gcValues, GDK_GC_FUNCTION | GDK_GC_FOREGROUND);
+}
+
+void map_draw_gdk(map_t* pMap, GPtrArray* pTiles, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags)
{
TIMER_BEGIN(maptimer, "BEGIN RENDER MAP (gdk)");
@@ -86,29 +103,40 @@ void map_draw_gdk(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixm
// 2.1. Draw Background
// map_draw_gdk_background(pMap, pPixmap);
+ gint nStyleZoomLevel = g_sZoomLevels[pRenderMetrics->nZoomLevel-1].nStyleZoomLevel;
+
// 2.2. Draw layer list in reverse order (painter's algorithm: http://en.wikipedia.org/wiki/Painter's_algorithm )
for(i=pMap->pLayersArray->len-1 ; i>=0 ; i--) {
maplayer_t* pLayer = g_ptr_array_index(pMap->pLayersArray, i);
if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_FILL) {
map_draw_gdk_layer_fill(pMap, pPixmap, pRenderMetrics,
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]); // style
+ pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]); // style
}
else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_LINES) {
- map_draw_gdk_layer_lines(pMap, pPixmap, pRenderMetrics,
- pMap->apLayerData[pLayer->nDataSource]->pRoadsArray, // data
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]); // style
+ gint iTile;
+ for(iTile=0 ; iTile < pTiles->len ; iTile++) {
+ maptile_t* pTile = g_ptr_array_index(pTiles, iTile);
+ map_draw_gdk_layer_lines(pMap, pPixmap, pRenderMetrics,
+ pTile->apMapObjectArrays[pLayer->nDataSource], // data
+ pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]); // style
+ }
}
else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_POLYGONS) {
- map_draw_gdk_layer_polygons(pMap, pPixmap, pRenderMetrics,
- pMap->apLayerData[pLayer->nDataSource]->pRoadsArray, // data
- pLayer->paStylesAtZoomLevels[pRenderMetrics->nZoomLevel-1]); // style
+ gint iTile;
+ for(iTile=0 ; iTile < pTiles->len ; iTile++) {
+ maptile_t* pTile = g_ptr_array_index(pTiles, iTile);
+ map_draw_gdk_layer_polygons(pMap, pPixmap, pRenderMetrics,
+ pTile->apMapObjectArrays[pLayer->nDataSource], // data
+ pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]); // style
+ }
}
else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_LOCATIONS) {
- map_draw_gdk_locations(pMap, pPixmap, pRenderMetrics);
+// map_draw_gdk_locations(pMap, pPixmap, pRenderMetrics);
}
- else if(pLayer->nDrawType == MAP_LAYER_RENDERTYPE_LOCATION_LABELS) {
- //map_draw_gdk_locations(pMap, pPixmap, pRenderMetrics);
+ else {
+// g_print("pLayer->nDrawType = %d\n", pLayer->nDrawType);
+// g_assert_not_reached();
}
}
}
@@ -148,7 +176,6 @@ static void map_draw_gdk_layer_polygons(map_t* pMap, GdkPixmap* pPixmap, renderm
map_draw_gdk_set_color(pGC, &(pLayerStyle->clrPrimary));
}
-
for(iString=0 ; iString<pRoadsArray->len ; iString++) {
pRoad = g_ptr_array_index(pRoadsArray, iString);
@@ -156,25 +183,27 @@ static void map_draw_gdk_layer_polygons(map_t* pMap, GdkPixmap* pPixmap, renderm
continue;
}
- if(pRoad->pMapPointsArray->len >= 2) {
- GdkPoint aPoints[MAX_GDK_LINE_SEGMENTS];
+ if(pRoad->pMapPointsArray->len < 3) {
+ //g_warning("not drawing polygon with < 3 points\n");
+ continue;
+ }
- if(pRoad->pMapPointsArray->len > MAX_GDK_LINE_SEGMENTS) {
- g_warning("not drawing line with > %d segments\n", MAX_GDK_LINE_SEGMENTS);
- continue;
- }
+ if(pRoad->pMapPointsArray->len > MAX_GDK_LINE_SEGMENTS) {
+ //g_warning("not drawing polygon with > %d points\n", MAX_GDK_LINE_SEGMENTS);
+ continue;
+ }
- // XXX: the bounding box should be pre-calculated!!!!
- for(iPoint=0 ; iPoint<pRoad->pMapPointsArray->len ; iPoint++) {
- pPoint = &g_array_index(pRoad->pMapPointsArray, mappoint_t, iPoint);
+ GdkPoint aPoints[MAX_GDK_LINE_SEGMENTS];
- aPoints[iPoint].x = pLayerStyle->nPixelOffsetX + (gint)SCALE_X(pRenderMetrics, pPoint->fLongitude);
- aPoints[iPoint].y = pLayerStyle->nPixelOffsetY + (gint)SCALE_Y(pRenderMetrics, pPoint->fLatitude);
- }
+ for(iPoint=0 ; iPoint<pRoad->pMapPointsArray->len ; iPoint++) {
+ pPoint = &g_array_index(pRoad->pMapPointsArray, mappoint_t, iPoint);
+
+ aPoints[iPoint].x = pLayerStyle->nPixelOffsetX + (gint)SCALE_X(pRenderMetrics, pPoint->fLongitude);
+ aPoints[iPoint].y = pLayerStyle->nPixelOffsetY + (gint)SCALE_Y(pRenderMetrics, pPoint->fLatitude);
+ }
- gdk_draw_polygon(pPixmap, pMap->pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->pTargetWidget)],
- TRUE, aPoints, pRoad->pMapPointsArray->len);
- }
+ gdk_draw_polygon(pPixmap, pMap->pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->pTargetWidget)],
+ TRUE, aPoints, pRoad->pMapPointsArray->len);
}
if(pLayerStyle->pGlyphFill != NULL) {
// Restore fill style
@@ -259,23 +288,26 @@ static void map_draw_gdk_layer_lines(map_t* pMap, GdkPixmap* pPixmap, rendermetr
}
if(pRoad->pMapPointsArray->len > MAX_GDK_LINE_SEGMENTS) {
- //g_warning("not drawing line with > %d segments\n", MAX_GDK_LINE_SEGMENTS);
+ //g_warning("not drawing line with > %d points\n", MAX_GDK_LINE_SEGMENTS);
+ continue;
+ }
+
+ if(pRoad->pMapPointsArray->len < 2) {
+ //g_warning("not drawing line with < 2 points\n");
continue;
}
- if(pRoad->pMapPointsArray->len >= 2) {
- // Copy all points into this array. Yuuup this is slow. :)
- GdkPoint aPoints[MAX_GDK_LINE_SEGMENTS];
+ // Copy all points into this array. Yuuup this is slow. :)
+ GdkPoint aPoints[MAX_GDK_LINE_SEGMENTS];
- for(iPoint=0 ; iPoint<pRoad->pMapPointsArray->len ; iPoint++) {
- pPoint = &g_array_index(pRoad->pMapPointsArray, mappoint_t, iPoint);
+ for(iPoint=0 ; iPoint<pRoad->pMapPointsArray->len ; iPoint++) {
+ pPoint = &g_array_index(pRoad->pMapPointsArray, mappoint_t, iPoint);
- aPoints[iPoint].x = pLayerStyle->nPixelOffsetX + (gint)SCALE_X(pRenderMetrics, pPoint->fLongitude);
- aPoints[iPoint].y = pLayerStyle->nPixelOffsetY + (gint)SCALE_Y(pRenderMetrics, pPoint->fLatitude);
- }
+ aPoints[iPoint].x = pLayerStyle->nPixelOffsetX + (gint)SCALE_X(pRenderMetrics, pPoint->fLongitude);
+ aPoints[iPoint].y = pLayerStyle->nPixelOffsetY + (gint)SCALE_Y(pRenderMetrics, pPoint->fLatitude);
+ }
- gdk_draw_lines(pPixmap, pMap->pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->pTargetWidget)], aPoints, pRoad->pMapPointsArray->len);
- }
+ gdk_draw_lines(pPixmap, pMap->pTargetWidget->style->fg_gc[GTK_WIDGET_STATE(pMap->pTargetWidget)], aPoints, pRoad->pMapPointsArray->len);
}
}
diff --git a/src/map_draw_gdk.h b/src/map_draw_gdk.h
index ccf0bdc..85240b4 100644
--- a/src/map_draw_gdk.h
+++ b/src/map_draw_gdk.h
@@ -26,6 +26,7 @@
#include <gdk/gdk.h>
-void map_draw_gdk(map_t* pMap, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags);
+void map_draw_gdk(map_t* pMap, GPtrArray* pTiles, rendermetrics_t* pRenderMetrics, GdkPixmap* pPixmap, gint nDrawFlags);
+void map_draw_gdk_xor_rect(map_t* pMap, GdkDrawable* pTargetDrawable, screenrect_t* pRect);
#endif
diff --git a/src/map_style.c b/src/map_style.c
index 7c508eb..bcf4870 100644
--- a/src/map_style.c
+++ b/src/map_style.c
@@ -30,6 +30,9 @@
#include "glyph.h"
#include "map_style.h"
+#define MIN_STYLE_LEVEL (1)
+#define MAX_STYLE_LEVEL (10)
+
// utility functions for iterating through lists of XML things
#define EACH_ATTRIBUTE_OF_NODE(a,n) (a) = (n)->properties ; (a) != NULL ; (a) = (a)->next
#define EACH_CHILD_OF_NODE(c,n) (c) = (n)->children ; (c) != NULL ; (c) = (c)->next
@@ -127,12 +130,12 @@ gboolean map_style_parse_zoomlevel(const gchar* pszZoomLevel, gint* pnReturnMinZ
nMin = nMax = atoi(pszStr);
}
- if(nMin < MIN_ZOOM_LEVEL || nMin > MAX_ZOOM_LEVEL) {
- g_warning("zoom-level '%s' out of valid range (%d to %d)\n", pszZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
+ if(nMin < MIN_STYLE_LEVEL || nMin > MAX_STYLE_LEVEL) {
+ g_warning("zoom-level '%s' out of valid range (%d to %d)\n", pszZoomLevel, MIN_STYLE_LEVEL, MAX_STYLE_LEVEL);
bReturn = FALSE;
}
- else if(nMax < MIN_ZOOM_LEVEL || nMax > MAX_ZOOM_LEVEL) {
- g_warning("zoom-level '%s' out of valid range (%d to %d)\n", pszZoomLevel, MIN_ZOOM_LEVEL, MAX_ZOOM_LEVEL);
+ else if(nMax < MIN_STYLE_LEVEL || nMax > MAX_STYLE_LEVEL) {
+ g_warning("zoom-level '%s' out of valid range (%d to %d)\n", pszZoomLevel, MIN_STYLE_LEVEL, MAX_STYLE_LEVEL);
bReturn = FALSE;
}
else {
@@ -375,8 +378,8 @@ map_style_parse_layer_property(map_t* pMap, xmlDocPtr pDoc, maplayer_t *pLayer,
}
}
- gint nMinZoomLevel = MIN_ZOOM_LEVEL;
- gint nMaxZoomLevel = MAX_ZOOM_LEVEL;
+ gint nMinZoomLevel = MIN_STYLE_LEVEL;
+ gint nMaxZoomLevel = MAX_STYLE_LEVEL;
if(pszZoomLevel != NULL) {
map_style_parse_zoomlevel(pszZoomLevel, &nMinZoomLevel, &nMaxZoomLevel);
}
diff --git a/src/map_tile.c b/src/map_tile.c
deleted file mode 100644
index 80da339..0000000
--- a/src/map_tile.c
+++ /dev/null
@@ -1,4 +0,0 @@
-void map_tile_load()
-{
-
-}
diff --git a/src/map_tile.h b/src/map_tile.h
deleted file mode 100644
index 8b13789..0000000
--- a/src/map_tile.h
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/map_tilemanager.c b/src/map_tilemanager.c
new file mode 100644
index 0000000..d5d75ec
--- /dev/null
+++ b/src/map_tilemanager.c
@@ -0,0 +1,404 @@
+/***************************************************************************
+ * map_tilemanager.c
+ *
+ * Copyright 2005 Ian McIntosh
+ * ian_mcintosh@linuxadvocate.org
+ ****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <gtk/gtk.h>
+#include "util.h"
+#include "map_tilemanager.h"
+#include "db.h"
+#include "road.h"
+
+// Prototypes
+static void _map_tilemanager_tile_load_map_objects(maptile_t* pTile, maprect_t* pRect, gint nLOD);
+static maptile_t* map_tilemanager_tile_cache_lookup(maptilemanager_t* pTileManager, maprect_t* pRect, gint nLOD);
+static maptile_t* map_tilemanager_tile_new(maptilemanager_t* pTileManager, maprect_t* pRect, gint nLOD);
+
+struct {
+ gdouble fShift; // the units we care about (eg. 1000 = 1000ths of a degree)
+ gint nModulus; // how many of the above units each tile is on a side
+ gdouble fWidth; // width and height of a tile, in degrees
+} g_aTileSizeAtLevelOfDetail[MAP_NUM_LEVELS_OF_DETAIL] = {
+ {1000.0, 70, 70.0 / 1000.0},
+ {100.0, 35, 35.0 / 100.0},
+ {10.0, 35, 35.0 / 10.0},
+ {1.0, 100, 100.0 / 1.0},
+};
+
+// Public API
+maptilemanager_t* map_tilemanager_new()
+{
+ maptilemanager_t* pNew = g_new0(maptilemanager_t, 1);
+
+ gint i;
+ for(i=0 ; i<MAP_NUM_LEVELS_OF_DETAIL ; i++) {
+ pNew->apTileCachedArrays[i] = g_ptr_array_new();
+ }
+ return pNew;
+}
+
+GPtrArray* map_tilemanager_load_tiles_for_worldrect(maptilemanager_t* pTileManager, maprect_t* pRect, gint nLOD)
+{
+ //
+ // Break the worldrect up into the aligned squares that we load
+ //
+ gdouble fTileShift = g_aTileSizeAtLevelOfDetail[nLOD].fShift;
+ gint nTileModulus = g_aTileSizeAtLevelOfDetail[nLOD].nModulus;
+ gdouble fTileWidth = g_aTileSizeAtLevelOfDetail[nLOD].fWidth;
+
+ gint32 nLatStart = (gint32)(pRect->A.fLatitude * fTileShift);
+ // round it DOWN (south)
+ if(pRect->A.fLatitude > 0) {
+ nLatStart -= (nLatStart % nTileModulus);
+ }
+ else {
+ nLatStart -= (nLatStart % nTileModulus);
+ nLatStart -= nTileModulus;
+ }
+
+ gint32 nLonStart = (gint32)(pRect->A.fLongitude * fTileShift);
+ // round it DOWN (west)
+ if(pRect->A.fLongitude > 0) {
+ nLonStart -= (nLonStart % nTileModulus);
+ }
+ else {
+ nLonStart -= (nLonStart % nTileModulus);
+ nLonStart -= nTileModulus;
+ }
+
+ gint32 nLatEnd = (gint32)(pRect->B.fLatitude * fTileShift);
+ // round it UP (north)
+ if(pRect->B.fLatitude > 0) {
+ nLatEnd -= (nLatEnd % nTileModulus);
+ nLatEnd += nTileModulus;
+ }
+ else {
+ nLatEnd -= (nLatEnd % nTileModulus);
+ }
+
+ gint32 nLonEnd = (gint32)(pRect->B.fLongitude * fTileShift);
+ // round it UP (east)
+ if(pRect->B.fLongitude > 0) {
+ nLonEnd -= (nLonEnd % nTileModulus);
+ nLonEnd += nTileModulus;
+ }
+ else {
+ nLonEnd -= (nLonEnd % nTileModulus);
+ }
+
+ // how many tiles are we loading in each direction?
+ gint nLatNumTiles = (nLatEnd - nLatStart) / nTileModulus;
+ gint nLonNumTiles = (nLonEnd - nLonStart) / nTileModulus;
+
+ gdouble fLatStart = (gdouble)nLatStart / fTileShift;
+ gdouble fLonStart = (gdouble)nLonStart / fTileShift;
+
+ if(fLatStart > pRect->A.fLatitude) {
+ g_print("fLatStart %f > pRect->A.fLatitude %f\n", fLatStart, pRect->A.fLatitude);
+ g_assert(fLatStart <= pRect->A.fLatitude);
+ }
+ if(fLonStart > pRect->A.fLongitude) {
+ g_print("fLonStart %f > pRect->A.fLongitude %f!!\n", fLonStart, pRect->A.fLongitude);
+ g_assert_not_reached();
+ }
+
+ GPtrArray* pTileArray = g_ptr_array_new();
+ g_assert(pTileArray);
+
+ gint nLat,nLon;
+ for(nLat = 0 ; nLat < nLatNumTiles ; nLat++) {
+ for(nLon = 0 ; nLon < nLonNumTiles ; nLon++) {
+
+ maprect_t rect;
+ rect.A.fLatitude = fLatStart + ((gdouble)(nLat) * fTileWidth);
+ rect.A.fLongitude = fLonStart + ((gdouble)(nLon) * fTileWidth);
+ rect.B.fLatitude = fLatStart + ((gdouble)(nLat+1) * fTileWidth);
+ rect.B.fLongitude = fLonStart + ((gdouble)(nLon+1) * fTileWidth);
+
+ maptile_t* pTile = map_tilemanager_tile_cache_lookup(pTileManager, &rect, nLOD);
+ if(pTile) {
+ // cache hit
+ g_ptr_array_add(pTileArray, pTile);
+ }
+ else {
+ // cache miss
+ pTile = map_tilemanager_tile_new(pTileManager, &rect, nLOD);
+ g_ptr_array_add(pTileArray, pTile);
+ }
+ }
+ }
+ return pTileArray;
+}
+
+static maptile_t* map_tilemanager_tile_new(maptilemanager_t* pTileManager, maprect_t* pRect, gint nLOD)
+{
+ //g_print("New tile for (%f,%f),(%f,%f)\n", pRect->A.fLongitude, pRect->A.fLatitude, pRect->B.fLongitude, pRect->B.fLatitude);
+
+ maptile_t* pNewTile = g_new0(maptile_t, 1);
+ memcpy(&(pNewTile->rcWorldBoundingBox), pRect, sizeof(maprect_t));
+
+ gint i;
+ for(i=0 ; i<MAP_NUM_OBJECT_TYPES ; i++) {
+ pNewTile->apMapObjectArrays[i] = g_ptr_array_new();
+ }
+ g_print("(");
+ _map_tilemanager_tile_load_map_objects(pNewTile, pRect, nLOD);
+// _map_tilemanager_tile_load_locations(pNewTile, pRect);
+ g_print(")");
+
+ // Add to cache
+ g_ptr_array_add(pTileManager->apTileCachedArrays[nLOD], pNewTile);
+ return pNewTile;
+}
+
+//
+// Private functions
+//
+static maptile_t* map_tilemanager_tile_cache_lookup(maptilemanager_t* pTileManager, maprect_t* pRect, gint nLOD)
+{
+ // XXX: this should not match on rect, it should match on LOD and Tile ID only
+ GPtrArray* pArray = pTileManager->apTileCachedArrays[nLOD];
+
+ gint i;
+ for(i=0 ; i<pArray->len ; i++) {
+ maptile_t* pTile = g_ptr_array_index(pArray, i);
+
+ if(map_math_maprects_equal(&(pTile->rcWorldBoundingBox), pRect)) {
+ //g_print("Cache hit\n");
+ return pTile;
+ }
+ }
+ //g_print("cache miss for (%f,%f),(%f,%f)\n", pRect->A.fLongitude, pRect->A.fLatitude, pRect->B.fLongitude, pRect->B.fLatitude);
+ return NULL;
+}
+
+static void _map_tilemanager_tile_load_map_objects(maptile_t* pTile, maprect_t* pRect, gint nLOD)
+{
+ db_resultset_t* pResultSet = NULL;
+ db_row_t aRow;
+
+ TIMER_BEGIN(mytimer, "BEGIN Geometry LOAD");
+
+ gchar* pszRoadTableName = g_strdup_printf("Road%d", nLOD);
+
+ // generate SQL
+ gchar azCoord1[20], azCoord2[20], azCoord3[20], azCoord4[20], azCoord5[20], azCoord6[20], azCoord7[20], azCoord8[20];
+ gchar* pszSQL;
+ pszSQL = g_strdup_printf(
+ "SELECT 0 AS ID, %s.TypeID, AsBinary(%s.Coordinates), RoadName.Name, RoadName.SuffixID %s"
+ " FROM %s "
+ " LEFT JOIN RoadName ON (%s.RoadNameID=RoadName.ID)"
+ " WHERE"
+ " MBRIntersects(GeomFromText('Polygon((%s %s,%s %s,%s %s,%s %s,%s %s))'), Coordinates)",
+
+ //pszRoadTableName, no ID column
+ pszRoadTableName, pszRoadTableName,
+
+ // Load all details for LOD 0
+ (nLOD == 0) ? ", AddressLeftStart, AddressLeftEnd, AddressRightStart, AddressRightEnd" : "",
+ pszRoadTableName, pszRoadTableName,
+
+ // upper left
+ g_ascii_dtostr(azCoord1, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord2, 20, pRect->A.fLongitude),
+ // upper right
+ g_ascii_dtostr(azCoord3, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord4, 20, pRect->B.fLongitude),
+ // bottom right
+ g_ascii_dtostr(azCoord5, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord6, 20, pRect->B.fLongitude),
+ // bottom left
+ g_ascii_dtostr(azCoord7, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord8, 20, pRect->A.fLongitude),
+ // upper left again
+ azCoord1, azCoord2);
+
+ //g_print("sql: %s\n", pszSQL);
+
+ db_query(pszSQL, &pResultSet);
+ g_free(pszSQL);
+ g_free(pszRoadTableName);
+
+ TIMER_SHOW(mytimer, "after query");
+
+ guint32 uRowCount = 0;
+ if(pResultSet) {
+ while((aRow = db_fetch_row(pResultSet))) {
+ uRowCount++;
+
+ // aRow[0] is ID
+ // aRow[1] is TypeID
+ // aRow[2] is Coordinates in mysql's text format
+ // aRow[3] is road name
+ // aRow[4] is road name suffix id
+ // aRow[5] is road address left start
+ // aRow[6] is road address left end
+ // aRow[7] is road address right start
+ // aRow[8] is road address right end
+
+ // Get layer type that this belongs on
+ gint nTypeID = atoi(aRow[1]);
+ if(nTypeID < MAP_OBJECT_TYPE_FIRST || nTypeID > MAP_OBJECT_TYPE_LAST) {
+ g_warning("geometry record '%s' has bad type '%s'\n", aRow[0], aRow[1]);
+ continue;
+ }
+
+ //road_t* pNewRoad = NULL;
+ //road_alloc(&pNewRoad);
+ road_t* pNewRoad = g_new0(road_t, 1);
+
+ // Build name by adding suffix, if one is present
+ if(aRow[3] != NULL && aRow[4] != NULL) {
+ const gchar* pszSuffix = road_suffix_itoa(atoi(aRow[4]), ROAD_SUFFIX_LENGTH_SHORT);
+ pNewRoad->pszName = g_strdup_printf("%s%s%s", aRow[3], (pszSuffix[0] != '\0') ? " " : "", pszSuffix);
+ }
+ else {
+ pNewRoad->pszName = g_strdup(""); // XXX: could we maybe not do this?
+ }
+ // We only load this st
+ if(nLOD == MAP_LEVEL_OF_DETAIL_BEST) {
+ pNewRoad->nAddressLeftStart = atoi(aRow[5]);
+ pNewRoad->nAddressLeftEnd = atoi(aRow[6]);
+ pNewRoad->nAddressRightStart = atoi(aRow[7]);
+ pNewRoad->nAddressRightEnd = atoi(aRow[8]);
+ }
+
+ // perhaps let the wkb parser create the array (at the perfect size)
+ pNewRoad->pMapPointsArray = g_array_new(FALSE, FALSE, sizeof(road_t));
+ db_parse_wkb_linestring(aRow[2], pNewRoad->pMapPointsArray, &(pNewRoad->rWorldBoundingBox));
+
+#ifdef ENABLE_RIVER_TO_LAKE_LOADTIME_HACK // XXX: combine this and above hack and you get lakes with squiggly edges. whoops. :)
+ if(nTypeID == MAP_OBJECT_TYPE_RIVER) {
+ mappoint_t* pPointA = &g_array_index(pNewRoad->pMapPointsArray, mappoint_t, 0);
+ mappoint_t* pPointB = &g_array_index(pNewRoad->pMapPointsArray, mappoint_t, pNewRoad->pMapPointsArray->len-1);
+
+ if(pPointA->fLatitude == pPointB->fLatitude && pPointA->fLongitude == pPointB->fLongitude) {
+ nTypeID = MAP_OBJECT_TYPE_LAKE;
+ }
+ }
+#endif
+
+ // Add this item to layer's list of pointstrings
+ g_ptr_array_add(pTile->apMapObjectArrays[nTypeID], pNewRoad);
+ } // end while loop on rows
+ //g_print("[%d rows]\n", uRowCount);
+ TIMER_SHOW(mytimer, "after rows retrieved");
+
+ db_free_result(pResultSet);
+ TIMER_SHOW(mytimer, "after free results");
+ TIMER_END(mytimer, "END Geometry LOAD");
+ }
+}
+
+// static gboolean map_data_load_locations(map_t* pMap, maprect_t* pRect)
+// {
+// g_return_val_if_fail(pMap != NULL, FALSE);
+// g_return_val_if_fail(pRect != NULL, FALSE);
+//
+// // if(map_get_zoomlevel(pMap) < MIN_ZOOM_LEVEL_FOR_LOCATIONS) {
+// // return TRUE;
+// // }
+//
+// TIMER_BEGIN(mytimer, "BEGIN Locations LOAD");
+//
+// // generate SQL
+// gchar* pszSQL;
+// gchar azCoord1[20], azCoord2[20], azCoord3[20], azCoord4[20], azCoord5[20], azCoord6[20], azCoord7[20], azCoord8[20];
+// pszSQL = g_strdup_printf(
+// "SELECT Location.ID, Location.LocationSetID, AsBinary(Location.Coordinates), LocationAttributeValue_Name.Value" // LocationAttributeValue_Name.Value is the "Name" field of this Location
+// " FROM Location"
+// " LEFT JOIN LocationAttributeValue AS LocationAttributeValue_Name ON (LocationAttributeValue_Name.LocationID=Location.ID AND LocationAttributeValue_Name.AttributeNameID=%d)"
+// " WHERE"
+// " MBRIntersects(GeomFromText('Polygon((%s %s,%s %s,%s %s,%s %s,%s %s))'), Coordinates)",
+// LOCATION_ATTRIBUTE_ID_NAME, // attribute ID for 'name'
+// // upper left
+// g_ascii_dtostr(azCoord1, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord2, 20, pRect->A.fLongitude),
+// // upper right
+// g_ascii_dtostr(azCoord3, 20, pRect->A.fLatitude), g_ascii_dtostr(azCoord4, 20, pRect->B.fLongitude),
+// // bottom right
+// g_ascii_dtostr(azCoord5, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord6, 20, pRect->B.fLongitude),
+// // bottom left
+// g_ascii_dtostr(azCoord7, 20, pRect->B.fLatitude), g_ascii_dtostr(azCoord8, 20, pRect->A.fLongitude),
+// // upper left again
+// azCoord1, azCoord2);
+// //g_print("sql: %s\n", pszSQL);
+//
+// db_resultset_t* pResultSet = NULL;
+// db_query(pszSQL, &pResultSet);
+// g_free(pszSQL);
+//
+// TIMER_SHOW(mytimer, "after query");
+//
+// guint32 uRowCount = 0;
+// if(pResultSet) {
+// db_row_t aRow;
+// while((aRow = db_fetch_row(pResultSet))) {
+// uRowCount++;
+//
+// // aRow[0] is ID
+// // aRow[1] is LocationSetID
+// // aRow[2] is Coordinates in mysql's binary format
+// // aRow[3] is Name
+//
+// // Get layer type that this belongs on
+// gint nLocationSetID = atoi(aRow[1]);
+//
+// // Extract
+// location_t* pNewLocation = NULL;
+// location_alloc(&pNewLocation);
+//
+// pNewLocation->nID = atoi(aRow[0]);
+//
+// // Parse coordinates
+// db_parse_wkb_point(aRow[2], &(pNewLocation->Coordinates));
+//
+// // make a copy of the name field, or "" (never leave it == NULL)
+// pNewLocation->pszName = g_strdup(aRow[3] != NULL ? aRow[3] : "");
+// map_store_location(pMap, pNewLocation, nLocationSetID);
+// } // end while loop on rows
+// //g_print("[%d rows]\n", uRowCount);
+// TIMER_SHOW(mytimer, "after rows retrieved");
+//
+// db_free_result(pResultSet);
+// TIMER_END(mytimer, "END Locations LOAD");
+// return TRUE;
+// }
+// else {
+// TIMER_END(mytimer, "END Locations LOAD (0 results)");
+// return FALSE;
+// }
+// }
+//
+// static void map_data_clear(map_t* pMap)
+// {
+// // Clear layers
+// gint i,j;
+// for(i=0 ; i<G_N_ELEMENTS(pMap->apLayerData) ; i++) {
+// maplayer_data_t* pLayerData = pMap->apLayerData[i];
+//
+// // Free each
+// for(j = (pLayerData->pRoadsArray->len - 1) ; j>=0 ; j--) {
+// road_t* pRoad = g_ptr_array_remove_index_fast(pLayerData->pRoadsArray, j);
+// g_array_free(pRoad->pMapPointsArray, TRUE);
+// g_free(pRoad);
+// }
+// g_assert(pLayerData->pRoadsArray->len == 0);
+// }
+//
+// // Clear locations
+// map_init_location_hash(pMap);
+// }
diff --git a/src/map_tilemanager.h b/src/map_tilemanager.h
new file mode 100644
index 0000000..f00fd22
--- /dev/null
+++ b/src/map_tilemanager.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ * map_tilemanager.h
+ *
+ * Copyright 2005 Ian McIntosh
+ * ian_mcintosh@linuxadvocate.org
+ ****************************************************************************/
+
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _MAP_TILEMANAGER_H_
+#define _MAP_TILEMANAGER_H_
+
+#include <gtk/gtk.h>
+
+typedef struct {
+ GPtrArray* apTileCachedArrays[4]; // MAP_NUM_LEVELS_OF_DETAIL
+} maptilemanager_t;
+
+#include "map.h"
+
+typedef struct {
+ maprect_t rcWorldBoundingBox;
+ GPtrArray* apMapObjectArrays[ MAP_NUM_OBJECT_TYPES + 1 ];
+} maptile_t;
+
+maptilemanager_t* map_tilemanager_new();
+
+// returns GArray containing maptile_t types
+GPtrArray* map_tilemanager_load_tiles_for_worldrect(maptilemanager_t* pTileManager, maprect_t* pWorldRect, gint nLOD);
+
+#endif
diff --git a/src/road.c b/src/road.c
index 8833101..5d5c5a0 100644
--- a/src/road.c
+++ b/src/road.c
@@ -22,11 +22,7 @@
*/
#include <gtk/gtk.h>
-#include "main.h"
#include "road.h"
-#include "util.h"
-#include "map.h"
-#include "gfreelist.h"
struct {
gchar* pszLong;
diff --git a/src/scenemanager.c b/src/scenemanager.c
index 2cd5646..9c91650 100644
--- a/src/scenemanager.c
+++ b/src/scenemanager.c
@@ -21,23 +21,18 @@
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-#include <gtk/gtk.h>
-
-#include "main.h"
-#include "scenemanager.h"
-
-#define ENABLE_NO_DUPLICATE_LABELS
-
/*
Goals:
- Keep text labels and other screen objects from overlapping
- Prevent the same text from showing up too often (currently not more than once)
*/
-void scenemanager_init(void)
-{
-
-}
+#include <gtk/gtk.h>
+
+#include "main.h"
+#include "scenemanager.h"
+
+#define ENABLE_NO_DUPLICATE_LABELS
void scenemanager_new(scenemanager_t** ppReturn)
{
diff --git a/src/scenemanager.h b/src/scenemanager.h
index 9cae407..831058a 100644
--- a/src/scenemanager.h
+++ b/src/scenemanager.h
@@ -38,7 +38,6 @@ typedef struct scenemanager {
GHashTable* pLabelHash;
} scenemanager_t;
-void scenemanager_init(void);
void scenemanager_new(scenemanager_t** ppReturn);
void scenemanager_set_screen_dimensions(scenemanager_t* pSceneManager, gint nWindowWidth, gint nWindowHeight);
diff --git a/src/search_road.c b/src/search_road.c
index 0724a5e..a46f16d 100644
--- a/src/search_road.c
+++ b/src/search_road.c
@@ -34,7 +34,7 @@
#include "glyph.h"
#include "searchwindow.h" // for defines about glyph size
-#define ROAD_RESULT_SUGGESTED_ZOOMLEVEL (4)
+#define ROAD_RESULT_SUGGESTED_ZOOMLEVEL (37)
#define FORMAT_ROAD_RESULT_WITHOUT_NUMBER ("%s %s\n%s")
#define FORMAT_ROAD_RESULT_WITH_NUMBER ("%d %s %s\n%s")
@@ -214,10 +214,10 @@ void search_road_on_roadsearch_struct(const roadsearch_t* pRoadSearch)
if(pRoadSearch->nNumber != ROADSEARCH_NUMBER_NONE) {
pszAddressClause = g_strdup_printf(
" AND ("
- "(%d BETWEEN Road.AddressLeftStart AND Road.AddressLeftEnd)"
- " OR (%d BETWEEN Road.AddressLeftEnd AND Road.AddressLeftStart)"
- " OR (%d BETWEEN Road.AddressRightStart AND Road.AddressRightEnd)"
- " OR (%d BETWEEN Road.AddressRightEnd AND Road.AddressRightStart)"
+ "(%d BETWEEN Road0.AddressLeftStart AND Road0.AddressLeftEnd)"
+ " OR (%d BETWEEN Road0.AddressLeftEnd AND Road0.AddressLeftStart)"
+ " OR (%d BETWEEN Road0.AddressRightStart AND Road0.AddressRightEnd)"
+ " OR (%d BETWEEN Road0.AddressRightEnd AND Road0.AddressRightStart)"
")", pRoadSearch->nNumber, pRoadSearch->nNumber,
pRoadSearch->nNumber, pRoadSearch->nNumber);
}
@@ -238,7 +238,7 @@ void search_road_on_roadsearch_struct(const roadsearch_t* pRoadSearch)
gchar* pszZIPClause;
if(pRoadSearch->pszZIPCode != NULL) {
gchar* pszSafeZIP = db_make_escaped_string(pRoadSearch->pszZIPCode);
- pszZIPClause = g_strdup_printf(" AND (Road.ZIPCodeLeft='%s' OR Road.ZIPCodeRight='%s')", pszSafeZIP, pszSafeZIP);
+ pszZIPClause = g_strdup_printf(" AND (Road0.ZIPCodeLeft='%s' OR Road0.ZIPCodeRight='%s')", pszSafeZIP, pszSafeZIP);
db_free_escaped_string(pszSafeZIP);
}
else {
@@ -247,7 +247,7 @@ void search_road_on_roadsearch_struct(const roadsearch_t* pRoadSearch)
gchar* pszCityClause;
if(pRoadSearch->nCityID != 0) {
- pszCityClause = g_strdup_printf(" AND (Road.CityLeftID=%d OR Road.CityRightID=%d)", pRoadSearch->nCityID, pRoadSearch->nCityID);
+ pszCityClause = g_strdup_printf(" AND (Road0.CityLeftID=%d OR Road0.CityRightID=%d)", pRoadSearch->nCityID, pRoadSearch->nCityID);
}
else {
pszCityClause = g_strdup("");
@@ -280,19 +280,19 @@ void search_road_on_roadsearch_struct(const roadsearch_t* pRoadSearch)
//pszRoadNameCondition = g_strdup_printf("RoadName.NameSoundex = SUBSTRING(SOUNDEX('%s') FROM 1 FOR 10)", pszSafeRoadName);
gchar* pszQuery = g_strdup_printf(
- "SELECT Road.ID, RoadName.Name, RoadName.SuffixID, AsBinary(Road.Coordinates), Road.AddressLeftStart, Road.AddressLeftEnd, Road.AddressRightStart, Road.AddressRightEnd, CityLeft.Name, CityRight.Name"
- ", StateLeft.Code, StateRight.Code, Road.ZIPCodeLeft, Road.ZIPCodeRight"
+ "SELECT 0 AS ID, RoadName.Name, RoadName.SuffixID, AsBinary(Road0.Coordinates), Road0.AddressLeftStart, Road0.AddressLeftEnd, Road0.AddressRightStart, Road0.AddressRightEnd, CityLeft.Name, CityRight.Name"
+ ", StateLeft.Code, StateRight.Code, Road0.ZIPCodeLeft, Road0.ZIPCodeRight"
" FROM RoadName"
- " LEFT JOIN Road ON (RoadName.ID=Road.RoadNameID%s)" // address # clause
+ " LEFT JOIN Road0 ON (RoadName.ID=Road0.RoadNameID%s)" // address # clause
// left side
- " LEFT JOIN City AS CityLeft ON (Road.CityLeftID=CityLeft.ID)"
+ " LEFT JOIN City AS CityLeft ON (Road0.CityLeftID=CityLeft.ID)"
" LEFT JOIN State AS StateLeft ON (CityLeft.StateID=StateLeft.ID)"
// right side
- " LEFT JOIN City AS CityRight ON (Road.CityRightID=CityRight.ID)"
+ " LEFT JOIN City AS CityRight ON (Road0.CityRightID=CityRight.ID)"
" LEFT JOIN State AS StateRight ON (CityRight.StateID=StateRight.ID)"
" WHERE %s"
// " WHERE RoadName.Name='%s'"
- " AND Road.ID IS NOT NULL" // don't include rows where the Road didn't match
+ " AND Road0.TypeID IS NOT NULL" // (any field will do) don't include rows where the Road didn't match
// begin clauses
"%s"
"%s"
@@ -353,7 +353,7 @@ void search_road_on_roadsearch_struct(const roadsearch_t* pRoadSearch)
db_parse_wkb_linestring(aRow[3], pMapPointsArray, &r);
search_road_filter_result(aRow[1], pRoadSearch->nNumber, atoi(aRow[2]), atoi(aRow[4]), atoi(aRow[5]), atoi(aRow[6]), atoi(aRow[7]), aRow[8], aRow[9], aRow[10], aRow[11], aRow[12], aRow[13], pMapPointsArray);
//g_print("%03d: Road.ID='%s' RoadName.Name='%s', Suffix=%s, L:%s-%s, R:%s-%s\n", nCount, aRow[0], aRow[1], aRow[3], aRow[4], aRow[5], aRow[6], aRow[7]);
-
+
g_array_free(pMapPointsArray, TRUE);
}
}