[cairo-commit] roadster/src map_draw_cairo.c, 1.28,
1.29 map_hittest.c, NONE, 1.1 map_hittest.h, NONE,
1.1 map_style.c, 1.5, 1.6
Ian McIntosh
commit at pdx.freedesktop.org
Wed Oct 5 17:00:55 PDT 2005
Committed by: ian
Update of /cvs/cairo/roadster/src
In directory gabe:/tmp/cvs-serv23483/src
Modified Files:
map_draw_cairo.c map_style.c
Added Files:
map_hittest.c map_hittest.h
Log Message:
* src/map_hittest.c:
* src/map_hittest.h: Added missing files.
Index: map_draw_cairo.c
===================================================================
RCS file: /cvs/cairo/roadster/src/map_draw_cairo.c,v
retrieving revision 1.28
retrieving revision 1.29
diff -u -d -r1.28 -r1.29
--- map_draw_cairo.c 5 Oct 2005 06:09:36 -0000 1.28
+++ map_draw_cairo.c 6 Oct 2005 00:00:53 -0000 1.29
@@ -172,9 +172,13 @@
}
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[nStyleZoomLevel-1]);
+ gint iTile;
+ for(iTile=0 ; iTile < pTiles->len ; iTile++) {
+ maptile_t* pTile = g_ptr_array_index(pTiles, iTile);
+ map_draw_cairo_layer_polygon_labels(pMap, pCairo, pRenderMetrics,
+ pTile->apMapObjectArrays[pLayer->nDataSource], // data
+ pLayer->paStylesAtZoomLevels[nStyleZoomLevel-1]);
+ }
}
}
}
--- NEW FILE: map_hittest.c ---
/***************************************************************************
* map_hittest.c
*
* Copyright 2005 Ian McIntosh
* ian_mcintosh at 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 <math.h>
#include "map.h"
#include "map_hittest.h"
#include "map_math.h"
#include "road.h"
#include "location.h"
#include "locationset.h"
static gboolean map_hittest_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pHitPoint, gdouble fMaxDistance, mappoint_t* pReturnClosestPoint, gdouble* pfReturnPercentAlongLine);
static ESide map_hittest_side_test_line(mappoint_t* pPoint1, mappoint_t* pPoint2, mappoint_t* pClosestPointOnLine, mappoint_t* pHitPoint);
static gboolean map_hittest_locations(map_t* pMap, rendermetrics_t* pRenderMetrics, GPtrArray* pLocationsArray, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
static gboolean map_hittest_locationsets(map_t* pMap, rendermetrics_t* pRenderMetrics, mappoint_t* pHitPoint, maphit_t** ppReturnStruct);
// ========================================================
// Hit Testing
// ========================================================
gboolean map_hittest(map_t* pMap, mappoint_t* pMapPoint, maphit_t** ppReturnStruct)
{
#if 0 // GGGGGGGGGGGGGGGG
rendermetrics_t rendermetrics;
map_get_render_metrics(pMap, &rendermetrics);
if(map_hittest_locationselections(pMap, &rendermetrics, pMap->pLocationSelectionArray, pMapPoint, ppReturnStruct)) {
return TRUE;
}
if(map_hittest_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_hittest_layer_roads(pMap->apLayerData[nLayer]->pRoadsArray, fMaxDistance, pMapPoint, ppReturnStruct)) {
return TRUE;
}
// otherwise try next layer...
}
#endif
return FALSE;
}
void map_hittest_maphit_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);
}
static gboolean map_hittest_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_hittest_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_hittest_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;
}
static ESide map_hittest_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_hittest_locationsets(map_t* pMap, rendermetrics_t* pRenderMetrics, mappoint_t* pHitPoint, maphit_t** ppReturnStruct)
{
gdouble fMaxDistance = map_pixels_to_degrees(pMap, 1, pMap->uZoomLevel) * 3; // XXX: don't hardcode distance :)
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_hittest_locations(pMap, pRenderMetrics, pLocationsArray, pHitPoint, ppReturnStruct)) {
return TRUE;
}
}
else {
// none loaded
}
}
return FALSE;
}
// hit-test an array of locations
static gboolean map_hittest_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;
return TRUE;
}
}
return FALSE;
}
static gboolean map_hittest_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(map_math_screenpoint_in_screenrect(&screenpoint, &(pLocationSelection->InfoBoxRect))) {
// fill out a new maphit_t struct with details
maphit_t* pHitStruct = g_new0(maphit_t, 1);
if(map_math_screenpoint_in_screenrect(&screenpoint, &(pLocationSelection->InfoBoxCloseRect))) {
pHitStruct->eHitType = MAP_HITTYPE_LOCATIONSELECTION_CLOSE;
pHitStruct->pszText = g_strdup("close");
pHitStruct->LocationSelectionHit.nLocationID = pLocationSelection->nLocationID;
}
else if(map_math_screenpoint_in_screenrect(&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(map_math_screenpoint_in_screenrect(&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;
}
// Does the given point come close enough to the line segment to be considered a hit?
static gboolean map_hittest_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);
}
return TRUE;
}
}
return FALSE;
}
--- NEW FILE: map_hittest.h ---
/***************************************************************************
* map_hittest.h
*
* Copyright 2005 Ian McIntosh
* ian_mcintosh at 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_HITTEST_H_
#define _MAP_HITTEST_H_
#include "map.h"
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;
gboolean map_hittest(map_t* pMap, mappoint_t* pMapPoint, maphit_t** ppReturnStruct);
void map_hittest_maphit_free(map_t* pMap, maphit_t* pHitStruct);
#endif
Index: map_style.c
===================================================================
RCS file: /cvs/cairo/roadster/src/map_style.c,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -d -r1.5 -r1.6
--- map_style.c 5 Oct 2005 06:09:36 -0000 1.5
+++ map_style.c 6 Oct 2005 00:00:53 -0000 1.6
@@ -82,7 +82,7 @@
if(pMap->pLayersArray != NULL) {
g_warning("reloading styles currently leaks memory so... don't do it very often :)\n");
- pMap->pLayersArray = NULL;
+ pMap->pLayersArray = NULL; // XXX: memory leak :)
}
pMap->pLayersArray = g_ptr_array_new();
More information about the cairo-commit
mailing list