diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 8 | ||||
-rw-r--r-- | src/db.c | 88 | ||||
-rw-r--r-- | src/db.h | 9 | ||||
-rw-r--r-- | src/import_tiger.c | 168 | ||||
-rw-r--r-- | src/main.c | 4 | ||||
-rw-r--r-- | src/main.h | 2 | ||||
-rw-r--r-- | src/mainwindow.c | 202 | ||||
-rw-r--r-- | src/map.c | 1138 | ||||
-rw-r--r-- | src/map.h | 77 | ||||
-rw-r--r-- | src/map_draw_cairo.c | 54 | ||||
-rw-r--r-- | src/map_draw_cairo.h | 2 | ||||
-rw-r--r-- | src/map_draw_gdk.c | 110 | ||||
-rw-r--r-- | src/map_draw_gdk.h | 3 | ||||
-rw-r--r-- | src/map_style.c | 15 | ||||
-rw-r--r-- | src/map_tile.c | 4 | ||||
-rw-r--r-- | src/map_tile.h | 1 | ||||
-rw-r--r-- | src/map_tilemanager.c | 404 | ||||
-rw-r--r-- | src/map_tilemanager.h | 45 | ||||
-rw-r--r-- | src/road.c | 4 | ||||
-rw-r--r-- | src/scenemanager.c | 17 | ||||
-rw-r--r-- | src/scenemanager.h | 1 | ||||
-rw-r--r-- | src/search_road.c | 28 |
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\ @@ -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," @@ -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); + } } } @@ -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(); @@ -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; } @@ -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 @@ -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 @@ -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); } } |