Monday, January 3, 2011

 

Two great maps that taste great together

I spent yesterday adding OSMDroid support to iTravelFree; here's a little precis of what I learned about letting users swap back and forth between Google Maps and OpenStreetMaps within the same Android Activity.

Why? you might ask: well, two reasons. One, OpenStreetMaps is a pretty cool project. Two, OSMDroid lets you access OpenStreetMaps even while offline, which is pretty awesome for a travel app, and while Google Maps App 5.0 has some offline support, it not you decides which tiles get cached. Mind you, creating and downloading OSM maps to Android is non-trivial, though the next iTravelFree release will make it much easier.

Also, it's just kinda interesting swapping back and forth between GMaps and OSM and seeing the differences between the two.

Anyway: how to do it? With a basic Decorator pattern, ie we roll our own MapView object (in my case, ITRMapView) which wraps both a Google MapView and and OpenStreetMapView, and forwards method calls appropriately depending on its state.

Arguably the best implementation would be via a common ITRMapViewImplementation interface and a concrete class for every map type. (If there were more than two map types, this would no longer really be arguable.) But I implemented it quick-and-dirty, with a bunch of if statements, which does at least have the serendipitous effect of being easy to use as an example.

Anyhoo, without further ado, here's the code:

/**
* Wrapper class which lets a MapActivity switch back and forth between Google Maps and OpenStreetMaps.
*/
public class ITRMapView {
private MapActivity mapActivity;
private BalloonLayout balloon;

private ITRGMapView gmap;
public ITRGMapView getGMap()
{
if (gmap==null) {
gmap = new ITRGMapView(mapActivity, "my_api_key");
gmap.setClickable(true);
gmap.setMapActivity(mapActivity);
gmap.setBuiltInZoomControls(true);
gmapLocation = new com.google.android.maps.MyLocationOverlay(mapActivity, gmap);
balloon = BalloonLayout.GetBalloonFor(mapActivity);
}
return gmap;
}
private com.google.android.maps.MyLocationOverlay gmapLocation;
private ListingsOverlay gmapListingsOverlay;

private OpenStreetMapView osm;
public OpenStreetMapView getOSMMap() { return osm; }
private org.andnav.osm.views.overlay.MyLocationOverlay osmLocation;
private OpenStreetMapViewItemizedOverlay<OpenStreetMapViewOverlayItem> osmListingsOverlay;

private boolean osmActive=false;
public boolean isOSMActive() { return osmActive; }
public void setOSMActive(boolean doOSM) { osmActive=doOSM; }

public ITRMapView(MapActivity activity) {
mapActivity=activity;
}

public void setOSMMap(OpenStreetMapView theOSMMap) {
osm=theOSMMap;
osmLocation = new org.andnav.osm.views.overlay.MyLocationOverlay(mapActivity, osm);
int centerLat = gmap==null ? 0 : gmap.getMapCenter().getLatitudeE6();
int centerLon = gmap==null ? 0 : gmap.getMapCenter().getLongitudeE6();
int zoom = gmap==null ? 10 : gmap.getZoomLevel();
org.andnav.osm.util.GeoPoint center = new org.andnav.osm.util.GeoPoint(centerLat, centerLon);
osm.getController().setCenter(center);
osm.getController().setZoom(zoom);
osm.setUpFor(mapActivity);
}

public void onResume() {
if (gmapLocation!=null) {
gmapLocation.enableMyLocation();
gmapLocation.enableCompass();
}
if (osmLocation!=null) {
osmLocation.enableMyLocation();
osmLocation.enableCompass();
}
}
public void onPause() {
if (gmapLocation!=null) {
gmapLocation.disableMyLocation();
gmapLocation.disableCompass();
}
if (osmLocation!=null) {
osmLocation.disableMyLocation();
osmLocation.disableCompass();
}
}

public void centerAndZoom(int centerLat, int centerLon, int spanLat, int spanLon) {
Log.i(""+this, "Center and zoom: "+centerLat+" "+centerLon+" "+spanLat+" "+spanLon+" osm "+osmActive);
if (osmActive) {
org.andnav.osm.util.GeoPoint center = new org.andnav.osm.util.GeoPoint(centerLat, centerLon);
OpenStreetMapViewController controller = osm.getController();
controller.setCenter(center);

//quick-and-dirty, because zoomToSpan doesn't work
Integer maxSpan = Math.max(spanLat, spanLon);
Double ln = Math.log(maxSpan.doubleValue());
Integer zoomLevel = 20-ln.intValue();
if (zoomLevel>5)
zoomLevel+=2;
if (zoomLevel>18)
zoomLevel=18;
controller.setZoom(zoomLevel);
}
else {
com.google.android.maps.GeoPoint center = new com.google.android.maps.GeoPoint(centerLat, centerLon);
MapController controller = gmap.getController();
controller.setCenter(center);
controller.zoomToSpan(spanLat, spanLon);
}
}

public int getMapCenterLatE6() {
if (osmActive) {
int lat = osm.getMapCenterLatitudeE6();
//Need to clamp to be consistent with GMaps
if (lat<-180000000)
lat+=360000000;
if (lat>180000000)
lat-=360000000;
return lat;
}
return gmap.getMapCenter().getLatitudeE6();
}
public int getMapCenterLonE6() {
if (osmActive) {
int lon = osm.getMapCenterLongitudeE6();
//Need to clamp to be consistent with GMaps
if (lon<-180000000)
lon+=360000000;
if (lon>180000000)
lon-=360000000;
return lon;
}
return gmap.getMapCenter().getLongitudeE6();
}

public void fillMap(ProgressIndicator indicator, HashSet<MappableItem> mCurrentSubset) {
if (osmActive)
fillOSMMap(indicator, mCurrentSubset);
else
fillGMap(indicator, mCurrentSubset);
}
synchronized void fillOSMMap(ProgressIndicator indicator, HashSet<MappableItem> mCurrentSubset) {
if (mCurrentSubset==null)
return;

ArrayList<OpenStreetMapViewOverlayItem> items = new ArrayList<OpenStreetMapViewOverlayItem>();
MappableItem[] toMap = mCurrentSubset.toArray(new MappableItem[mCurrentSubset.size()]);
int listEnd=Math.min(toMap.length, Settings.GetMapMax());
for (int i=0; i<listEnd; i++) {
OpenStreetMapViewOverlayItem newItem = new OpenStreetMapViewOverlayItem(
toMap[i].getIDString(),
toMap[i].getMapDetailText(),
new GeoPoint(toMap[i].getLatitudeE6(), toMap[i].getLongitudeE6()));
newItem.setMarker(toMap[i].getMarker(mapActivity));
items.add(newItem);
if (indicator!=null)
indicator.showProgress(i, listEnd);
}

osmListingsOverlay = new OpenStreetMapViewItemizedOverlay<OpenStreetMapViewOverlayItem>(
mapActivity,items,
new OpenStreetMapViewItemizedOverlay.OnItemGestureListener<OpenStreetMapViewOverlayItem>(){
@Override
public boolean onItemSingleTapUp(int index, OpenStreetMapViewOverlayItem item) {
RelativeLayout rl = (RelativeLayout) osm.getParent();
rl.removeView(balloon);
balloon.disablePointer();
balloon.setUpTapFor(mapActivity, item.getTitle(), item.getSnippet());
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(200, 100);
params.addRule(RelativeLayout.ALIGN_PARENT_TOP);
params.addRule(RelativeLayout.CENTER_HORIZONTAL);
rl.addView(balloon, params);
return true;
}

@Override
public boolean onItemLongPress(int index, OpenStreetMapViewOverlayItem item) {
// Toast.makeText(mapActivity, "Item '" + item.mTitle + "' (index=" + index + ") got long pressed", Toast.LENGTH_LONG).show();
return false;
}
});
}
synchronized void fillGMap(ProgressIndicator indicator, HashSet<MappableItem> mCurrentSubset) {
if (mCurrentSubset==null)
return;

Drawable defaultMarker = mapActivity.getResources().getDrawable(R.drawable.map_fave);
gmapListingsOverlay = new ListingsOverlay(mapActivity, gmap, defaultMarker);
MappableItem[] toMap = mCurrentSubset.toArray(new MappableItem[mCurrentSubset.size()]);
int listEnd=Math.min(toMap.length, Settings.GetMapMax());
for (int i=0; i<listEnd; i++) {
Drawable marker = toMap[i].getMarker(mapActivity);
ListingOverlayItem overlay = toMap[i].getListingOverlayItem();
if (overlay!=null)
gmapListingsOverlay.addItem(overlay, marker);
if (indicator!=null)
indicator.showProgress(i, listEnd);
}
gmapListingsOverlay.doPopulate();
}

public void showListingsOverlay() {
if (osmActive) {
List<OpenStreetMapViewOverlay> mapOverlays = osm.getOverlays();
if (!mapOverlays.contains(osmListingsOverlay))
mapOverlays.add(osmListingsOverlay);
if (!mapOverlays.contains(osmLocation))
mapOverlays.add(osmLocation);
}
else {
List<Overlay> mapOverlays = gmap.getOverlays();
if (!mapOverlays.contains(gmapListingsOverlay))
mapOverlays.add(gmapListingsOverlay);
if (!mapOverlays.contains(gmapLocation))
mapOverlays.add(gmapLocation);
}
postInvalidate();
}

public void postInvalidate() {
if (osmActive)
osm.postInvalidate();
else
gmap.postInvalidate();
}

public int getLatitudeSpan() {
if (osmActive)
return osm.getLatitudeSpanE6();
return gmap.getLatitudeSpan();
}
public int getLongitudeSpan() {
if (osmActive)
return osm.getLongitudeSpanE6();
return gmap.getLongitudeSpan();
}
public int getZoomLevel() {
if (osmActive)
return osm.getZoomLevel();
return gmap.getZoomLevel();
}

}


And how is it used? Well, the MapActivity includes code like this:

private void switchMapMode() {
int lastLat = mapView.getMapCenterLatE6();
int lastLon = mapView.getMapCenterLonE6();
int lastLatSpan = mapView.getLatitudeSpan();
int lastLonSpan = mapView.getLatitudeSpan();
boolean willHandleCenterZoom = lastLat!=0 && lastLon!=0 && lastLatSpan!=0 && lastLonSpan!=0;

if (mapView.isOSMActive())
useGMap(!willHandleCenterZoom);
else
useOSMMap(!willHandleCenterZoom);

if (willHandleCenterZoom)
mapView.centerAndZoom(lastLat, lastLon, lastLatSpan, lastLonSpan);
}
private void useGMap(boolean doCenterZoom) {
mapView.setOSMActive(false);
setContentView(R.layout.map);
//remove map view from old parent, if any
ITRGMapView gmap = mapView.getGMap();
RelativeLayout rl = (RelativeLayout) gmap.getParent();
if (rl!=null)
rl.removeView(gmap);
//add map view to new parent
rl = (RelativeLayout) findViewById(R.id.mapLayout);
rl.addView(mapView.getGMap());
setUpMap(doCenterZoom);
}
private void useOSMMap(boolean doCenterZoom) {
mapView.setOSMActive(true);
setContentView(R.layout.map_osm);
mapView.setOSMMap((ITROSMMapView)findViewById(R.id.mapview_osm));
setUpMap(doCenterZoom);
new FillMapTask().execute();
}
private void setUpMap(boolean doCenterZoom) {
registerForContextMenu(findViewById(R.id.tagButton));
registerForContextMenu(findViewById(R.id.mapButton));
setUpToolbar();
if (doCenterZoom && mCurrentLat!=0 && mCurrentLong!=0 && mCurrentLatSpan!=0 && mCurrentLongSpan!=0)
mapView.centerAndZoom(mCurrentLat, mCurrentLong, mCurrentLatSpan, mCurrentLongSpan);
else if (doCenterZoom && mapView.isOSMActive() && mCurrentLat==0 && mCurrentLong==0)
mapView.centerAndZoom(51000000, 0, 1000000, 1000000);
}


Of some interest, I suppose, are the BalloonLayout and ListingsOverlay classes mentioned above, so:


public class BalloonLayout extends LinearLayout {
private boolean pointerEnabled=true;
public void disablePointer() { pointerEnabled=false; }
public void enablePointer() {pointerEnabled=true; }

public BalloonLayout(Context context) {
super(context);
}

public BalloonLayout(Context context, AttributeSet set) {
super(context, set);
}

@Override
protected void dispatchDraw(Canvas canvas) {

Paint panelPaint = new Paint();
panelPaint.setARGB(0, 0, 0, 0);
RectF baloonRect = new RectF();
baloonRect.set(0,0, getMeasuredWidth(), 2*(getMeasuredHeight()/3));
panelPaint.setARGB(230, 255, 255, 255);
canvas.drawRoundRect(baloonRect, 10, 10, panelPaint);

if (pointerEnabled) {
Path baloonTip = new Path();
baloonTip.moveTo(5*(getMeasuredWidth()/8), 2*(getMeasuredHeight()/3));
baloonTip.lineTo(getMeasuredWidth()/2, getMeasuredHeight());
baloonTip.lineTo(3*(getMeasuredWidth()/4), 2*(getMeasuredHeight()/3));
canvas.drawPath(baloonTip, panelPaint);
}

super.dispatchDraw(canvas);
}

public void setUpTapFor(final Activity activity, String title, String snippet)
{
setVisibility(View.VISIBLE);
TextView titleText = (TextView) findViewById(R.id.detailText);
titleText.setText(snippet);

ImageButton detailButton = (ImageButton) findViewById(R.id.detailGo);
try {
final Long id = Long.valueOf(title);
detailButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent intent = new Intent(activity, ListingActivity.class);
intent.putExtra(ListingActivity.KEY_ID, id);
activity.startActivityForResult(intent, 0);
//not pretty!
if (activity instanceof ITRMapActivity)
((ITRMapActivity)activity).editedListingID=id;
}
});
}
catch (NumberFormatException ex) {
ex.printStackTrace();
}

public static BalloonLayout GetBalloonFor(Activity activity) {
LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final BalloonLayout balloon = (BalloonLayout) layoutInflater.inflate(R.layout.map_balloon, null);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(200,100);
layoutParams.addRule(RelativeLayout.CENTER_VERTICAL);
layoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL);
balloon.setLayoutParams(layoutParams);

ImageButton closeButton = (ImageButton) balloon.findViewById(R.id.detailClose);
closeButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
balloon.setVisibility(View.GONE);
}
});
return balloon;
}
}


although I have to work the kinks out of placing the balloon on the OSM map still. As for the ListingsOverlay,


public class ListingsOverlay extends ItemizedOverlay<ListingOverlayItem> {
private MapActivity activity;
private ITRGMapView mapView;
private BalloonLayout balloon;
private ArrayList<ListingOverlayItem> items=new ArrayList<ListingOverlayItem>();
public ArrayList<ListingOverlayItem> getItems() { return items; }

public ListingsOverlay(MapActivity anActivity, ITRGMapView aMapView, android.graphics.drawable.Drawable defaultMarker) {
super(defaultMarker);
activity=anActivity;
mapView=aMapView;
balloon = BalloonLayout.GetBalloonFor(activity);
}

public void addItem(ListingOverlayItem item, Drawable marker) {
if (item!=null && marker!=null && !items.contains(item)) {
super.boundCenterBottom(marker);
item.setMarker(marker);
items.add(item);
}
}

public void doPopulate() {
populate();
setLastFocusedIndex(-1);
}

@Override
protected ListingOverlayItem createItem(int i) {
return(items.get(i));
}

@Override
public int size() {
return(items.size());
}

@Override
protected boolean onTap(int i) {
mapView.removeView(balloon);
balloon.enablePointer();
ListingOverlayItem item = items.get(i);
balloon.setUpTapFor(activity, item.getTitle(), item.getSnippet());
MapView.LayoutParams params = new MapView.LayoutParams(200, 100, item.getPoint(), MapView.LayoutParams.BOTTOM_CENTER);
mapView.addView(balloon, params);
return true;
}

}



I hereby BSD 2.0-release all the above code, if anyone wants to use it - enjoy!

Labels: , , , , ,


Comments:
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation during this lockdown period everyone can use it wisely.

Agile team facilitation

ICP ATF

 
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation during this lockdown period everyone can use it wisely.
Team facilitator Agile

Agile facilitator
 

Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation during this lockdown period everyone can use it wisely.
Team facilitator in Agile

ICAGILE ATF

 
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation ,everyone can use it wisely.

Agile team facilitation

ICAGILE ATF

 


Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation ,everyone can use it wisely.
ICP ATF

Agile facilitator

 
Thanks for sharing this informative content.,
Leanpitch provides online training in Agile team facilitation ,everyone can use it wisely.

Team facilitator in Agile

ICP ATF


 
Excellent content ,Thanks for sharing this .,
CSPO certification
CSPO TRAINING
 
This is the best article for reading and gets important information, reading the article is my hobby, but I really like read your article, amazing article thank you very much!!

사설토토
카지노사이트
파워볼
온라인카지노
 
Awesome! Its really amazing post, I have got much clear idea on the topic from this post.

스포츠토토
토토
안전놀이터
토토사이트

 
something that is remarkable and should be learned. Thank you for providing this great info theres so much to learn.

사설토토
카지노
파워볼사이트
바카라사이트

 
Thanks for sharing with us this important Content. I feel strongly about it and really enjoyed learning more about this topic.
카지노사이트

 
Thanks for sharing this marvelous post. I m very pleased to read this article.
카지노사이트

 
Its an amazing website, I really enjoy reading your articles.
스포츠토토

 
Such an amazing and helpful post. I really really love it.
바카라사이트

 
I blog often and I truly appreciate your content.
야설
Feel free to visit my blog :
야설


 
This great article has truly peaked my interest.
일본야동
Feel free to visit my blog : 일본야동

 
I’m going to bookmark your site and keep checking for new details about once per week.
국산야동
Feel free to visit my blog : 국산야동

 
I subscribed to your Feed too.
일본야동
Feel free to visit my blog : 일본야동

 
Hi there! This article could not be written much better!
야설
Feel free to visit my blog : 야설

 
Hello, I'm happy to see some great articles on your site. Would you like to come to my site later? My site also has posts, comments and communities similar to yours. Please visit and take a look 메이저놀이터

 
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business
사설경마

magosucowep

 
토토사이트 I cherished up to you’ll receive performed right here. The caricature is tasteful, your authored subject matter stylish. nevertheless, you command get got an impatience over that you want be delivering the following. sick certainly come more in the past once more as precisely the similar just about very often inside case you shield this hike.


 
Simply want to say your article is as astonishing. The clarity in your post is just great and i could assume you’re an expert on this subject. Well with your permission allow me to grab your RSS feed to keep updated with forthcoming post. Thanks a million and please continue the enjoyable work. 스포츠토토

 
Pretty portion of content. I just stumbled upon your weblog and in accession capital to claim that I acquire actually loved account your blog posts. Anyway I will be subscribing on your augment or even I fulfillment you get entry to constantly quickly. 카지노사이트

 
카지노사이트 You’ve got knowlege about everything that’s why I love this site! Very useful and helpful.. Try browsing my website to gather some information.. Will appreciated it a lot!


 
스포츠토토 Great beat ! I wish to apprentgice at tthe same time as you amend yor site, how can i subscribe for a blog website?
Thee account aided mee a applicable deal. I have been a little bit familiar of this your broadcast provided bright clear concept


 
Many thanks for the article, I have a lot of spray lining knowledge but always learn something new. Keep up the good work and thank you again. 먹튀사이트

 
I've been using WordPress on a number of websites for about a year and am worried about switching to another platform. I have heard good things about keonhacai. Is there a way I can transfer all my wordpress content into it? Any help would be really appreciated!

 
If a newbie enters a programming class, he needs to pick up the pace on time and finish off all the Java assignments on time as other students in the class. But it is not always possible for each and every student to finish the task on time. They need to handle other subjects too. But deadlines never wait for anyone, and if not submitted within the stipulated time, the assignments will get rejected by the professor. So, the student needs to clear the assignments on time. Hence, they look for Java programming assignment help at the hour of need.
 
This is a very interesting post. Thank you for posting a lot of interesting posts. And please visit my site!~! The name of the site is 슬롯사이트 .

 
From one day, I noticed that many people post a lot of articles related to 온라인슬롯 . Among them, I think your article is the best among them!!I

 
Yes i am completely concurred with this article and i simply need say this article is extremely decent and exceptionally useful article.I will make a point to be perusing your blog more. You made a decent point yet I can"t resist the urge to ponder, shouldn"t something be said about the other side? 먹튀검증업체 .

 
This is the perfect post.메이저토토사이트 It helped me a lot. If you have time, I hope you come to my site and share your opinions. Have a nice day.

 
Thanks for sharing such meaningful information. I am waiting for a long and feel energetic after reading such a post. I am looking forward to the genuine way to transform information from one place to another place. I am glad to link your website with the Assignment Help domain. I prefer to subscribe to our website so that any meaningful information cannot remain for further reading.
 
Hello, I'm happy to see some great articles on your site. Would you like to come to my site later? My site also has posts, comments and communities similar to yours. Please visit and take a look 메이저놀이터

 
Please keep on posting such quality articles as this is a rare thing to find these days. I am always searching online for posts that can help me. watching forward to another great blog. Good luck to the author! all the best! 스포츠토토사이트

 
Keep posting these best articles. If you are looking for the best cheap law essay writing service just turnout to Britain Paper for all assignment, essay and dissertation services at a cheap price.

 
Canon IJ Network Tool will get you through the network settings uninterruptedly. It is essentially required when you are attempting to get your printer connected to a different network because a new network tends to reset the printer’s existing network settings.The Canon IJ Printer Utility can be used to keep a check on your printer’s ink levels and cartridges and clean the ink tanks and paper feed rollers. Also, you can make adjustments to your Canon printer’s power settings.

 
Once you are done with the driver setup via canon.com/ijsetup , you will have to insert a pile of pages into the printer tray for printing the documents..

Visit ij.start.canon | ij.start canon and find out the best way to download Canon printer drivers.

All-in-one Canon Inkjet printers are suitable for home, business, school, and others to improve productivity. You can easily set up your Canon printer through drivers from Canon.com/ijsetup , wireless connection, USB, and a few components.

 
The ij.start.cannon setup process for every Canon model is almost similar, however the download through https //ij.start.cannon and http //ij.start.cannon installation process may differ. you can also visit canonsetup-canon.com/ijsetup website for same. Https //ij.start.cannon.Depending on your requirement, it offers a type printer including PIXMA, SELPHY, MAXIFY, etc. canon.com/ijsetup


 
Great work done. Nice website. Love it. This is really nice.
Hbomax/tvsignin
Disneyplus.com/Begin
Disneyplus com login begin
Cricut.com/setup
 
Canon drivers fully support and assists for all compatible products for all Window versions. We offer the required data to configure, utilize and install your Canon products on your Windows PC.canon is completely safe and secure. ij.start.canon |
ij.start.cannon
 
Keep sharing such good blogs,
If you're in need of the Essay helper Malaysia, then go to Malaysiaassignmenthelp.com and pay a small fee to have your essay written with excellent quality content by professional academic writers.
 
Howdy! Do you know if they make any plugins to assist with SEO? I’m trying to get my blog to rank for some targeted keywords but I’m not seeing very good results. If you know of any please share. Cheers! 안전토토사이트

 
Hire a professional academic writer for your assignment solutions. qqi assignments have Phd thesis writers that provide thesis writing services to global university scholars and help them in completing their assignments on time. So, don't hesitate to contact us anytime.
 
Because many students are unaware of the ramifications of plagiarised writing, they frequently inquire. Is it ok to pay for plagiarised assignment examples? It is always advisable to complete your own assignments or use an assignment writing service like Malaysiaassignmenthelp.com. By taking the time to develop original content, you may prevent the negative repercussions of plagiarism and ensure that your project is of the highest quality.
 
스포츠토토 I have read a few good stuff here. Definitely value bookmarking for revisiting.



 
I am happy that you simply shared this useful information with us. Please stay us up to date like this. Thanks for sharing.

바카라사이트
카지노사이트

 
appreciate it for your hard work. You should keep it up forever! Best of luck.

스포츠토토
먹튀검증

 
Very good post! We will be linking to this great article on our
site. Keep up the great writing.

토토사이트
스포츠중계


 
토토사이트
배트맨토토프로


I believe you have noted some very interesting points, appreciate it for the post.


 
สาวใหญ่เมียพี่ชายที่รัก

ตั้มอายุ 28 ปี มีพี่ชายอายุ 34 ปี แต่พี่ชายของตั้มได้เมียที่มีอายุมากกว่าตัวเองห่างกันถึง 10 ปีเลย พี่สะใภ้ของตั้มมีอายุ 44 ปี แต่ยังมีรูปร่างหน้าตาสวยยิ่งกว่าสาวๆ วัยรุ่น ด้วยผิวพรรณขาวเนียน หุ่นอวบอั๋น มันทำให้ตั้มแอบชอบพี่สะใภ้ สาวใหญ่ อย่างลับๆ เพราะถ้าเรื่องถึงหูพี่ชายมีหวังโดนตีเอาตายแน่ ตั้มมีความคิดอยสกลองเย็ดสาวใหญ่ ดูสักครั้งในชีวิตเพราะเวลา ดูคลิปโป๊ไทย ในเว็บแล้วมักเกิดอารมณ์เงี่ยนทุกครั้ง
 
Thanks for sharing this informative content.,
Prime Writing is the top leading Content Writing agency in UK we are best in seo copywriting services contact for the 24/7 support
 

Post a Comment

Subscribe to Post Comments [Atom]





<< Home

This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]