package edu.mit.mitmobile2.tour; import java.util.ArrayList; import java.util.List; import com.esri.core.geometry.GeometryEngine; import com.esri.core.geometry.SpatialReference; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Point; import android.graphics.drawable.BitmapDrawable; import android.location.Location; import android.util.DisplayMetrics; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.webkit.WebChromeClient; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ImageButton; import android.widget.ScrollView; import android.widget.TextView; import edu.mit.mitmobile2.AttributesParser; import edu.mit.mitmobile2.AudioPlayer; import edu.mit.mitmobile2.CommonActions; import edu.mit.mitmobile2.LockingScrollView; import edu.mit.mitmobile2.R; import edu.mit.mitmobile2.RemoteImageView; import edu.mit.mitmobile2.ResizableImageView; import edu.mit.mitmobile2.SliderInterface; import edu.mit.mitmobile2.tour.Tour.Directions; import edu.mit.mitmobile2.tour.Tour.GeoPoint; import edu.mit.mitmobile2.tour.Tour.HtmlContentNode; import edu.mit.mitmobile2.tour.Tour.PhotoInfo; import edu.mit.mitmobile2.tour.Tour.SideTrip; import edu.mit.mitmobile2.tour.Tour.Site; import edu.mit.mitmobile2.tour.Tour.TourItem; import edu.mit.mitmobile2.tour.Tour.TourItemContentNode; public class TourStopSliderInterface implements SliderInterface, OnClickListener { private Context mContext; private ScrollView mView; private TourItem mTourItem; private Tour mTour; private AudioPlayer mAudioPlayer; private ImageButton audioButton; private RemoteImageView mMainImageView; private boolean mIsSite; public TourStopSliderInterface(Context context, Tour tour, TourItem tourItem, AudioPlayer ap, TourProgressBar progbar, boolean isSite) { mContext = context; mTour = tour; mTourItem = tourItem; mAudioPlayer = ap; mIsSite = isSite; } @Override public LockingScrollView getVerticalScrollView() { return null; } @Override public View getView() { if(mView == null) { LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mView = (ScrollView) inflater.inflate(R.layout.tour_stop, null); audioButton = (ImageButton) mView.findViewById(R.id.tourVoiceOverButton); audioButton.setFocusable(false); if(mTourItem.getAudioUrl() != null) { audioButton.setOnClickListener(this); } else { audioButton.setVisibility(View.GONE); } mWebView = (WebView) mView.findViewById(R.id.tourStopWebView); mMainImageView = (RemoteImageView) mView.findViewById(R.id.tourStopPhoto); updateImage(); initializeMap(); } return mView; } @Override public void onDestroy() { // TODO Auto-generated method stub } @Override public void onSelected() { refreshImages(); } private WebView mWebView; protected com.esri.core.geometry.Point mCenterPoint; private SpatialReference mSpatialReferenceWGS84; private SpatialReference mSpatialReferenceWebMerc; private ResizableImageView mMapImageView; private void initializeMap() { if(mTourItem.getPath() != null) { mSpatialReferenceWGS84 = SpatialReference.create(WGS84_WKID); mSpatialReferenceWebMerc = SpatialReference.create(WEBMERC_WKID); mView.findViewById(R.id.tourDirectionsMapContainer).setVisibility(View.VISIBLE); mMapImageView = (ResizableImageView) mView.findViewById(R.id.tourDirectionsMapIV); final List<GeoPoint> geoPoints = mTourItem.getPath().getGeoPoints(); GeoRect geoRect = new GeoRect(geoPoints); GeoPoint center = geoRect.getCenter(); mCenterPoint = (com.esri.core.geometry.Point)GeometryEngine.project(new com.esri.core.geometry.Point(center.getLongitudeE6()/1000000., center.getLatitudeE6()/1000000.), mSpatialReferenceWGS84, mSpatialReferenceWebMerc); mMapImageView.setOnSizeChangedListener(new ResizableImageView.OnSizeChangedListener() { @Override public void onSizeChanged(final int w, final int h, int oldw, int oldh) { if (w == 0 || h == 0) { // map not ready to be drawn return; } // add a padding (subtract from image size) int padding = AttributesParser.parseDimension("1dip", mContext); int width = w - 2 * padding; int height = h - 2 * padding; mMapImageView.setURLs(getMapURLs(width, height)); // add the current location dot overlay mMapImageView.setOverlay(new ResizableImageView.Overlay() { @Override public void draw(Canvas canvas) { // draw single segment drawGeoPointsPath(canvas, R.dimen.tourSingleSegmentPathWidth, geoPoints, w, h); // draw complete path drawGeoPointsPath(canvas, R.dimen.tourPathWidth, mTour.getPathGeoPoints(), w, h); // draw stop markers BitmapDrawable firstImage = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.map_starting_arrow); BitmapDrawable lastImage = (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.map_ending_arrow); drawArrow(canvas, geoPoints.get(0), geoPoints.get(1), firstImage, true, w, h); drawArrow(canvas, geoPoints.get(geoPoints.size()-1), geoPoints.get(geoPoints.size()-2), lastImage, false, w, h); if(mLocation != null) { GeoPoint location = new GeoPoint((int) (mLocation.getLatitude() * 1000000), (int) (mLocation.getLongitude() * 1000000)); Point center = getPoint(location, w, h); // draw error circle Paint errorCirclePaint = new Paint(); errorCirclePaint.setARGB(127, 0, 0, 255); errorCirclePaint.setAntiAlias(true); float errorRadius = metersToPixels(mLocation.getAccuracy(), location); // do not show location if not on map or error radius is bigger than map if( center.x < 0 || center.x > w || center.y < 0 || center.y > h || errorRadius * 2 > Math.max(w, h) ) { return; } canvas.drawCircle(center.x, center.y, errorRadius, errorCirclePaint); // draw bullseye Paint centerPaint = new Paint(); centerPaint.setAntiAlias(true); centerPaint.setARGB(255, 0, 0, 255); canvas.drawCircle(center.x, center.y, 5, centerPaint); } } }); } }); if(mMapImageView.getHeight() > 0) { mMapImageView.notifyOnSizeChangedListener(); } } } private void drawGeoPointsPath(Canvas canvas, int widthResourceId, List<? extends GeoPoint> geoPoints, int width, int height) { // configure the path width/color Paint linePaint = new Paint(); linePaint.setColor(mContext.getResources().getColor(R.color.tourPathColor)); float lineWidth = mContext.getResources().getDimension(widthResourceId); linePaint.setStrokeWidth(lineWidth); linePaint.setStyle(Paint.Style.STROKE); Point start = getPoint(geoPoints.get(0), width, height); android.graphics.Path graphicsPath = new android.graphics.Path(); graphicsPath.moveTo(start.x, start.y); for(int i=1; i < geoPoints.size(); i++) { Point stop = getPoint(geoPoints.get(i), width, height); graphicsPath.lineTo(stop.x, stop.y); } // draw path canvas.drawPath(graphicsPath, linePaint); } private void drawArrow(Canvas canvas, GeoPoint firstGeoPoint, GeoPoint secondGeoPoint, BitmapDrawable stopBitmap, boolean outGoing, int width, int height) { Point firstPoint = getPoint(firstGeoPoint, width, height); Point secondPoint = getPoint(secondGeoPoint, width, height); float deltaX = secondPoint.x - firstPoint.x; float deltaY = secondPoint.y - firstPoint.y; if(!outGoing) { deltaX = -deltaX; deltaY = -deltaY; } float sin = (float) (deltaY / Math.sqrt(deltaX*deltaX + deltaY*deltaY)); float cos = (float) (deltaX / Math.sqrt(deltaX*deltaX + deltaY*deltaY)); Matrix matrix = new Matrix(); matrix.setSinCos(sin, cos); matrix.preTranslate(-stopBitmap.getIntrinsicWidth()/2, -stopBitmap.getIntrinsicHeight()/2); // move the arrow into a position so we can rotate about 0,0 matrix.postTranslate(firstPoint.x, firstPoint.y); // move arrow to the stop position Paint paint = new Paint(); paint.setAntiAlias(true); paint.setFilterBitmap(true); canvas.drawBitmap(stopBitmap.getBitmap(), matrix, paint); } private void updateImage() { if(mTourItem.getPath() == null) { View imageDividerView = mView.findViewById(R.id.tourStopPhotoDivider); if(mTourItem.getPhotoInfo() != null) { mMainImageView.setVisibility(View.VISIBLE); mMainImageView.setURL(mTourItem.getPhotoInfo().getPhotoUrl()); imageDividerView.setVisibility(View.VISIBLE); } } } public void refreshImages() { mWebView.loadUrl("javascript:refreshImages()"); mMainImageView.refresh(); if (mMapImageView != null) { mMapImageView.refresh(); } } @SuppressLint("SetJavaScriptEnabled") @Override public void updateView() { TextView titleView = (TextView) mView.findViewById(R.id.tourStopTitle); titleView.setText(mTourItem.getTitle()); mWebView.setFocusable(false); mWebView.getSettings().setJavaScriptEnabled(true); mWebView.getSettings().setLightTouchEnabled(true); mWebView.setWebViewClient(new WebViewClient() { @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if(url.startsWith("sidetrip://")) { String sideTripId = url.substring("sidetrip://".length()); Site site = null; if(mTourItem.getClass() == Site.class) { site = (Site) mTourItem; } else if(mTourItem.getClass() == Directions.class) { Directions directions = (Directions) mTourItem; site = directions.getSource(); } TourSideTripActivity.launch(mContext, site.getSiteGuid(), sideTripId, mIsSite); } else { CommonActions.viewURL(mContext, url); } return true; } }); mWebView.setWebChromeClient(new WebChromeClient() { @Override public void onConsoleMessage(String message, int lineNumber, String sourceID) { Log.d("TourSiteLocation", "TourItemId= " + mTourItem.getTitle() + " message + -- From line " + lineNumber + " of " + sourceID); } }); mWebView.loadDataWithBaseURL(null, webviewHtml(), "text/html", "utf-8", null); } public String webviewHtml() { String bodyHtml = ""; for(TourItemContentNode node : mTourItem.getContent().getContentNodes()) { if(node.getClass() == HtmlContentNode.class) { bodyHtml += node.getHtml(); } else { SideTrip sideTrip = (SideTrip) node; bodyHtml += TourHtml.sideTripLinkHtmlFragment(sideTrip); } } if(mTourItem.getPath() != null && mTourItem.getPhotoInfo() != null) { PhotoInfo photoInfo = mTourItem.getPhotoInfo(); return TourHtml.tourStopHtml(mContext, bodyHtml, photoInfo.getPhotoUrl(), photoInfo.getPhotoLabel()); } else { // no photo so just use the plain template return TourHtml.tourStopHtml(mContext, bodyHtml, null, null); } } @Override public void onClick(View v) { if(mTourItem.getAudioUrl() != null) { mAudioPlayer.togglePlay(mTourItem.getAudioUrl(), audioButton); } } Location mLocation; public void onLocationChanged(Location location) { mLocation = location; if (mMapImageView != null) { mMapImageView.invalidate(); } } private static final int WEBMERC_WKID = 102113; private static final int WGS84_WKID = 4326; private final float BASE_ZOOM = 17.1f; private com.esri.core.geometry.Point mTopLeft; private com.esri.core.geometry.Point mBottomRight; private List<String> getMapURLs(int width, int height) { DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); int zoom = mTourItem.getPath().getZoom(); double widthExtent = width * Math.pow(2, BASE_ZOOM - zoom) / metrics.density; double heightExtent = height * Math.pow(2, BASE_ZOOM - zoom) / metrics.density; double minX = mCenterPoint.getX() - widthExtent/2; double minY = mCenterPoint.getY() - heightExtent/2; double maxX = mCenterPoint.getX() + widthExtent/2; double maxY = mCenterPoint.getY() + heightExtent/2; String bbox = minX + "," + minY + "," + maxX + "," + maxY; mTopLeft = (com.esri.core.geometry.Point)GeometryEngine.project(new com.esri.core.geometry.Point(minX, minY), mSpatialReferenceWebMerc, mSpatialReferenceWGS84); mBottomRight = (com.esri.core.geometry.Point)GeometryEngine.project(new com.esri.core.geometry.Point(maxX, maxY), mSpatialReferenceWebMerc, mSpatialReferenceWGS84); ArrayList<String> urls = new ArrayList<String>(); urls.add("http://services.arcgisonline.com/ArcGIS/rest/services/World_Topo_Map/MapServer/export?format=png24&transparent=false&f=image&bbox=" + bbox + "&size=" + width + "," + height); urls.add("http://maps.mit.edu/pub/rest/services/basemap/WhereIs_Base_Topo/MapServer/export?format=png24&transparent=true&f=image&bbox=" + bbox + "&size=" + width + "," + height); return urls; } /* * Since ArcGIS calculation are way to slow we do interpolation * based on Top-Left and Bottom-Right points in WGS84 */ private Point getPoint(GeoPoint point, int width, int height) { double fractionX = (point.getLongitudeE6()/1000000. - mTopLeft.getX()) / (mBottomRight.getX()-mTopLeft.getX()); double fractionY = (point.getLatitudeE6()/1000000. - mTopLeft.getY()) / (mBottomRight.getY()-mTopLeft.getY()); int x = (int) ((fractionX) * width); int y = (int) ((1-fractionY) * height); return new Point(x, y); } static final double EARTH_RADIUS_METERS = 6370.0 * 1000.0; public float metersToPixels(float meters, GeoPoint geoPoint) { int zoom = mTourItem.getPath().getZoom(); DisplayMetrics metrics = mContext.getResources().getDisplayMetrics(); double meterPerPixel = Math.pow(2, BASE_ZOOM - zoom) / metrics.density; return (float) (meterPerPixel * meters); } }