Down at the End of Lonely Street

Where the hell is that?
Google Maps finds them scattered all over the place, but I haven’t found that useful search capability within the OSM environment. So I added such to my slippy map viewer and here’s how I did it with C++ and QT.

  1. Throw a non modal dialog with an edit box to enter your search term like “Lonely%” where the ‘%’ is the sql wild card. Closure of the dialog will terminate the find and display result operation.
  2. Detect the [Enter] key from the edit box and build an sql query that looks like:
    SELECT name, way FROM planet_osm_roads where ST_Intersects(way, …current screen bounding rectangle…) is true
    AND name LIKE ‘" … the search term from the edit box … "’
    Issue that query to the gis data base and extract the returned way data to a memory array of way coordinates. Free this array at dialog closure time;
  3. At paintEvent time draw your previously generated map tiles as you now do. Then, when ‘way’ data is present, draw the way data from step 2 on top of the tiles. What you will wind up with are highlighted ways as selected by the edit box. You may then zoom and swerve around in your map space all you choose and the way highlights will follow appropriately.

That 1,2,3 method is pretty simple, but of course there are details. So here are some clues that may help if you choose to add this search capability to your maps presentation system. I assume you already know how to make and use dialog boxes and to draw tiles on the screen so I’ll start with the routine that reacts to [Enter] press in the dialog search term edit box.

These entities are declared in the header


struct LLMMpoint{				//Lon/lat <-> Mx/My
double Lon;
double Lat;
double Mx;
double My;};
struct tLLsinglePoint{
double lon;
double lat;};
struct tLLextent{				//The bounds of your viewing area
double minLon;
double maxLon;
double minLat;
double maxLat;};
union{						//to represent binary as double
long int bin;
double flt;	}convert;
int FindMeNbrPoints;
tLLsinglePoint		*holdFindMePointArray;
tLLsinglePoint		*FindMePointArray;
tLLextent            	ScreenExtentLL;

End of header declarations


DialogFindMe(.....{			Make edit box and other stuff including:
holdFindMePointArray = (tLLsinglePoint *) malloc(MAX_NBR_FINDME_POINTS*sizeof(tLLsinglePoint));	//Plot data array
}

slotFindMeDone(){	//Come here on dialog close
free(holdFindMePointArray);
FindMeNbrPoints = 0;				//Signal to paintEvent - nothing to highlight
}

void DialogFindMe::slotFindMeFind()
{
....Setup stuff....
FindMeNbrPoints = 0;
FindMePointArray = holdindMePointArray;
const char *conninfo;
conninfo = "dbname = gis";
PGconn *conn = PQconnectdb(conninfo);	//Connect to the data base
if (PQstatus(conn) != CONNECTION_OK)
{
Too bad. Is  postgres up and running?
return;
}
			//Build SELECT query
QString q1 = "SELECT planet_osm_line.name, planet_osm_line.way from planet_osm_line ";
QString q2 = "WHERE ST_Intersects(way, St_SetSRID(St_MakeBox2D(St_SetSRID(St_Point";LLMMpoint LLul;		//Search box must be in x/y If your screen area is defined by lat/lon then convert like this
LLMMpoint LLul;					//Upper left
LLMMpoint LLlr;					//Lower right
LLul.Lon = ScreenExtentLL.minLon;
LLul.Lat = ScreenExtentLL.maxLat;
uConvert_WGS84_mercator(&LLul);		//See more about uConvert_WGS84_m.... below
LLlr.Lon = ScreenExtentLL.maxLon;
LLlr.Lat = tScreenExtentLL.minLat;
uConvert_WGS84_mercator(&LLlr);
//
QString q3 = QString("(%1,%2) ").arg(LLul.Mx,10,'f',0).arg(LLul.My,10,'f',0);
QString q4 = ",900913)::geometry, St_SetSRID(St_Point";
QString q5 = QString("(%1,%2) ").arg(LLlr.Mx,10,'f',0).arg(LLlr.My,10,'f',0);
QString q6 = ",900913)),900913)::geometry) is true ";
QString searchTerm = DialogEditFindMeName->text();	//Look for this		
QString q7 = "AND name LIKE '" + searchTerm + "' ";
QString q8 = "UNION select planet_osm_roads.name, planet_osm_roads.way from planet_osm_roads ";
QString query = q1+q2+q3+q4+q5+q6+q7+q8+q2+q3+q4+q5+q6+q7;
/*Which looks like
SELECT planet_osm_line.name, planet_osm_line.way from planet_osm_line
WHERE ST_Intersects(way, St_SetSRID(St_MakeBox2D(St_SetSRID(St_Point( -13657072,   4961284) ,900913)::geometry, St_SetSRID(St_Point( -13030900,   4648469) ,900913)),900913)::geometry) is true
AND name LIKE 'Lonely%'
UNION SELECT planet_osm_roads.name, planet_osm_roads.way from planet_osm_roads
WHERE ST_Intersects(way, St_SetSRID(St_MakeBox2D(St_SetSRID(St_Point( -13657072,   4961284) ,900913)::geometry, St_SetSRID(St_Point( -13030900,   4648469) ,900913)),900913)::geometry) is true
AND name LIKE 'Lonely%'
That!		Most, if not all, roads appear in the _line table. Rants to come later about data organization
*/
QByteArray ba = query.toLatin1();
const char *pQuery = ba.data();
PGresult *res = PQexec(conn,pQuery);		//Send SELECT query to server
if (PQresultStatus(res) == PGRES_TUPLES_OK)
{
	int nrows = PQntuples(res);				//Number of occurrences of 'name'
	if (nrows != 0) 							//This is the 'found' condition
	{
		for (int irow=0; irow<nrows; irow++)		//For each instance found in table
		{
			int iway = PQfnumber(res, "way");			//Will be 1 always, probably
			char* way  = PQgetvalue(res,irow,iway);		//Pointer to data
			way += 26;							//Skip 26 byte preamble
			int ilen = strlen(way);					//Should be a multiple of 16
/*
The 'way' data comes to you organized as 64 bit floating point numbers represented as 16 four bit hex nibbles.
Two nibbles, organized big-endiam, form into eight bytes, organized little-endian make a data point.
Here is how to unscramble that
*/
			long int iiii, nibble, byte;
			for (int j=0; j<ilen/16; j++)			//Number of bytes/16 = number of x or y points
			{
				iiii = 0;
				for (int i=0; i<16; i+=2)				//Convert to binary big endian
				{
					iiii >>= 8;
					iiii &= 0x00ffffffffffffff;
					if (*way >= 'A') nibble = (long int)(*way - 'A' +10);
					else             nibble = (long int)(*way - '0');
					byte = nibble << 4;
					way++;
					if (*way >= 'A') nibble = (long int)(*way - 'A' +10);
					else             nibble = (long int)(*way - '0');
					byte += nibble;
					iiii += (byte << 56);
					way++;
				}				//End extract 64 bits
			convert.bin = iiii;					//Now have a single x or y point
			double mercX, mercY;
			if (j % 2 == 0) mercX = convert.flt;		//Data comes in as x first
			else								//Then y and record both
			{
				mercY = convert.flt;
				tLLMMpoint wayPt;
				wayPt.Lat = wayPt.Lon = 0;		//Causes convert from xy to lat/lon
				wayPt.Mx = mercX;
				wayPt.My = mercY;
				uConvert_WGS84_mercator(&wayPt);
				FindMePointPoint->lat = wayPt.Lat;		//This is the end result
				FindMePointPoint->lon = wayPt.Lon;
				FindMePointPoint++;				//Point to next array position
				FindMeNbrPoints++;
			}									//End of got one x/y pair
		}										//End extract all points in one row
		FindMePointPoint->lon = FindMePointPoint->lat = 0;	//Place row end break
		FindMePointPoint++;	
		FindMeNbrPoints++;
	}											//End of query process
	FindMePointPoint->lon = FindMePointPoint->lat = 0;	//Place end of data break
	FindMePointPoint++;	
	FindMeNbrPoints++;
	PQclear(res);					//Release querry result
}								//End of tuples_ok
else {Too bad. Work on your query}	//Tupples not ok
PQfinish(conn);						//Release database connection
}								//end of slotFindMeFind()

Here are the lat/lon <-> x/y conversion equations mentioned above


uConvert_WGS84_mercator(tLLMMpoint *LonLatMyMx)		//Enter with lon/lat set in LonLatMyMx to convert to mercator
{												//Enter with lon/lat 0 and set LonLatMyMx to convert to lon/lat
	double earthRad = 6378137.0;		//Equatorial Radius, WGS84
	if (LonLatMyMx->Lon != 0 && LonLatMyMx->Lat !=0)		//Preference is to mercator
	{
		if ((qAbs(LonLatMyMx->Lon) > 180 || qAbs(LonLatMyMx->Lat) > 90))
		return;
		
		double num = LonLatMyMx->Lon * PI / 180.0;
		double x = earthRad * num; 
		double a = LonLatMyMx->Lat * PI / 180.0;
		LonLatMyMx->Mx = x;
		LonLatMyMx->My = earthRad / 2 * qLn((1.0 + qSin(a)) / (1.0 - qSin(a)));	//Exit with MyMx plugged in
	}
	else 													//Convert to lon/Lat_
	{
		double rMajor = earthRad; //Equatorial Radius, WGS84
		double shift  = PI * rMajor;
		double lat    = LonLatMyMx->My / shift * 180.0;
		LonLatMyMx->Lat = 180 / PI * (2 * atan(exp(lat * PI / 180.0)) - PI / 2.0);
		LonLatMyMx->Lon = LonLatMyMx->Mx / shift * 180.0;
	}
}

Now you have an in-memory array of all the points that make up a ‘way’ followed a 0,0 terminator,
followed by any additional found 'way’s followed by a second 0,0 terminator.

In your paintEvent paint your tiles as you normally do and then:


if (FindMeNbrPoints != 0)
{
	double dLatPix, dLonPix;
	int x,y;
	tLLsinglePoint *FindMePointPoint = FindMePointArray;	//Array Packing address
	pen.setColor(QColor::fromRgb(255,0,0));
	if (tScreenZoom < 12) pen.setWidth(10);			//Highlight finds for large area searches
	painter.setPen(pen);
	QPoint p1, p2;
	for (int i=0; i<FindMeNbrPoints; i++)
	{
		if (!i)									//First point of first way
		{
			featureLat = FindMePointPoint->lat;
			featureLon = FindMePointPoint->lon;
			dLatPix = (tScreenCenterLL.lat - featureLat) * (double)TILE_VPIX_NBR / LL.dLat;
			dLonPix = (tScreenCenterLL.lon - featureLon) * (double)TILE_VPIX_NBR / LL.dLon;
			x = tWindowSize.width()/2 - dLonPix-2;
			p1.setX(x);
			y = tWindowSize.height()/2 + dLatPix-2;
			p1.setY(y);
		
			FindMePointPoint++;
			featureLat = FindMePointPoint->lat;
			featureLon = FindMePointPoint->lon;
			dLatPix = (tScreenCenterLL.lat - featureLat) * (double)TILE_VPIX_NBR / LL.dLat;
			dLonPix = (tScreenCenterLL.lon - featureLon) * (double)TILE_VPIX_NBR / LL.dLon;
			x = tWindowSize.width()/2 - dLonPix-2;
			p2.setX(x);
			y = tWindowSize.height()/2 + dLatPix-2;
			p2.setY(y);
			painter.drawLine( p1,  p2);
		}
		else									//Continue with way
		{
			p1 = p2;
			FindMePointPoint++;
			featureLat = FindMePointPoint->lat;
			featureLon = FindMePointPoint->lon;
			if (featureLat == 0 && featureLon == 0)				//End of way
			{
				FindMePointPoint++;
				featureLat = FindMePointPoint->lat;
				featureLon = FindMePointPoint->lon;
				if (featureLat == 0 && featureLon == 0)			//End of it all
				{
					break;
				}
				i = -1;											//Start new way
			}
			dLatPix = (tScreenCenterLL.lat - featureLat) * (double)TILE_VPIX_NBR / LL.dLat;
			dLonPix = (tScreenCenterLL.lon - featureLon) * (double)TILE_VPIX_NBR / LL.dLon;
			x = tWindowSize.width()/2 - dLonPix-2;
			p2.setX(x);
			y = tWindowSize.height()/2 + dLatPix-2;
			p2.setY(y);
			if (i > 0) painter.drawLine( p1,  p2);			//Continue with way
		}
	}
}

Now zoom to level 7 centered on Lake Tahoe, CA/NV and search for ‘Lonely%’. See 1/2 dozen+ 'Lonely’s including an actual ‘Lonely Street’ in a rural subdivision up by Redding, CA and ‘Lonely Heart Court’ down in Las Vegas. The search takes a little over 30 seconds on my mid range system. Time is dependent on the size of your database as opposed to the size of the search area.

The above is heavily abridged. I apologize for any mysteries remaining.

-RickBrown in Reno, NV

So you tried really all listed web services from http://wiki.openstreetmap.org/wiki/Search_engines

and you had no success in your search results?

(I have not enough sourcecode reading skills, but is it an own “search engine” that you have posted above?)

On line search engines are superior; when you are on line. I regularly travel to places where there is no internet service and so have chosen to use a single map viewer system which is useful in both cases. My goal for this posting is to show how to add search capability to osm slippy map viewers.
Thanks for your interest.
-Rick