Search code examples
javaandroidxmlbitmapindoor-positioning-system

IndoorAtlas Android SDK 1.4.2-132: getting the floorplan image/bitmap in fullscreen + with rotation


So, apparently I am a newbie in Java, and the following is my story: I have been working with the IndoorAtlas (the company that provides Indoor Positioning Solution) Android SDK 1.4.2-132 and I am not getting the results I want when I test the application on my phone. Here is my full JAVA code which I mostly got from this link "Show FloorPlan and get Location with IndoorAtlas":

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PointF;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;

import com.indooratlas.android.CalibrationState;
import com.indooratlas.android.FloorPlan;
import com.indooratlas.android.FutureResult;
import com.indooratlas.android.ImagePoint;
import com.indooratlas.android.IndoorAtlas;
import com.indooratlas.android.IndoorAtlasException;
import com.indooratlas.android.IndoorAtlasFactory;
import com.indooratlas.android.ResultCallback;
import com.indooratlas.android.ServiceState;
import com.indooratlas.android.IndoorAtlasListener;

import java.io.IOException;

public class FloorPlanLoader extends Activity implements IndoorAtlasListener
{
    private IndoorAtlas ia;
    private FloorPlan floorPlan; //here it says private field "floorplan" is never assigned.
    private FloorPlan mFloorPlan; // here it says private field "mFloorPlan" is assigned but never accessed.
    private ImageView imageView;

    String venueId = "xxx";
    String floorId = "xxx";
    String floorPlanId = "xxx";
    String apiKey = "xxx";
    String apiSecret = "xxx";

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.indoor_positioning);
        initIndoorAtlas();
    }
    public void initIndoorAtlas() //method to initialize indooratlas instance
    {
        try
        {
            ia = IndoorAtlasFactory.createIndoorAtlas(getApplicationContext(), this, apiKey, apiSecret);
        }
        catch (IndoorAtlasException ex)
        {
            Log.e("IndoorAtlas", "initialisation failed", ex);
            log();
        }
        FutureResult<FloorPlan> result = ia.fetchFloorPlan(floorPlanId); //obtains instance of floor plan
        result.setCallback(new ResultCallback<FloorPlan>() {
            @Override
            public void onResult(final FloorPlan result) {
                mFloorPlan = result;
                loadFloorPlanImage(result);
            }

            @Override
            public void onSystemError(IOException e) {

            }

            @Override
            public void onApplicationError(IndoorAtlasException e) {

            }
        });
    }
    public void loadFloorPlanImage(FloorPlan floorPlan)  //Method to load floor plan from the server
    {
        BitmapFactory.Options options = createBitmapOptions(floorPlan);
        FutureResult<Bitmap> result= ia.fetchFloorPlanImage(floorPlan, options);
        result.setCallback(new ResultCallback<Bitmap>()
        {
            @Override
            public void onResult(final Bitmap result)
            {
                runOnUiThread(new Runnable()
                {
                    @Override
                    public void run() {

                        imageView = (ImageView) findViewById(R.id.imageView);
                        imageView.setImageBitmap(result);
                    }
                });
                updateImageViewInUiThread();
            }

            @Override
            public void onSystemError(IOException e)
            {
                log();
            }

            @Override
            public void onApplicationError(IndoorAtlasException e)
            {
                log();
            }
        });
        try
        {
            ia.startPositioning(venueId, floorId, floorPlanId);
        }
        catch (IndoorAtlasException e)
        {
            log();
        }
    }

   private BitmapFactory.Options createBitmapOptions(FloorPlan floorPlan)
   {
       BitmapFactory.Options options = new BitmapFactory.Options();
       int reqWidth = 2048;
       int reqHeight = 2048;

       final int width = (int) floorPlan.dimensions[0];
       final int height = (int) floorPlan.dimensions[1];
       int inSampleSize = 1;

       if (height > reqHeight || width > reqWidth) {

           final int halfHeight = height / 2;
           final int halfWidth = width / 2;

           while ((halfHeight / inSampleSize) > reqHeight
                   && (halfWidth / inSampleSize) > reqWidth) {
               inSampleSize *= 2;
           }

       }
       options.inSampleSize = inSampleSize;
       return options;
   }

    public void updateImageViewInUiThread() {}

    public void onServiceUpdate(ServiceState state) //method to show blue dot
    {
        int i = state.getImagePoint().getI();
        int j = state.getImagePoint().getJ();
        setImagePoint(state.getImagePoint());
        PointF scaledPoint = new PointF();
        Util.calculateScaledPoint((int) floorPlan.dimensions[0], (int) floorPlan.dimensions[1], i, j, imageView, scaledPoint);
        drawNewPositionInUiThread();
    }

    private void setImagePoint(final ImagePoint imgPt) {

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ImageView imagePoint = (ImageView) findViewById(R.id.bluedottwo);
                imagePoint.setX(imgPt.getI());
                imagePoint.setY(imgPt.getJ());
            }
        });
    }

    private void drawNewPositionInUiThread() {

    }
    public static class Util {
        public static float calculateScaleFactor(int originalWidth, int originalHeight,
                                                 ImageView imageView) {
            if (imageView.getScaleType() != ImageView.ScaleType.CENTER_INSIDE) {
                throw new IllegalArgumentException("only scale type of CENTER_INSIDE supported, was: "
                        + imageView.getScaleType());
            }

            final int availableX = imageView.getWidth()
                    - (imageView.getPaddingLeft() + imageView.getPaddingRight());
            final int availableY = imageView.getHeight()
                    - (imageView.getPaddingTop() + imageView.getPaddingBottom());

            if (originalWidth > availableX || originalHeight > availableY) {
                // original image would not fit without scaling
                return originalWidth > availableX
                        ? availableX / (float) originalWidth
                        : availableY / (float) originalHeight;
            } else {
                return 1f; // no scaling required
            }

        }
        public static void calculateScaledPoint(int originalWidth, int originalHeight,
                                                int x, int y,
                                                ImageView imageView,
                                                PointF point) {


            final float scale = calculateScaleFactor(originalWidth, originalHeight, imageView);
            final float scaledWidth = originalWidth * scale;
            final float scaledHeight = originalHeight * scale;

            // when image inside view is smaller than the view itself and image is centered (assumption)
            // there will be some empty space around the image (here offset)
            final float offsetX = Math.max(0, (imageView.getWidth() - scaledWidth) / 2);
            final float offsetY = Math.max(0, (imageView.getHeight() - scaledHeight) / 2);

            point.x = offsetX + (x * scale);
            point.y = offsetY + (y * scale);
        }
    }

    public void onServiceFailure(int errorCode, String reason)
    {
        log();
    }

    @Override
    public void onServiceInitializing()
    {
        log();
    }

    @Override
    public void onServiceInitialized()
    {
        log();
    }

    @Override
    public void onInitializationFailed(final String reason)
    {
        log();
    }

    @Override
    public void onServiceStopped()
    {
        log();
    }

    @Override
    public void onCalibrationStatus(CalibrationState calibrationState)
    {
        log();
    }

    @Override
    public void onCalibrationReady()
    {
        log();
    }

    @Override
    public void onNetworkChangeComplete(boolean success)
    {
    }

    @Override
    public void onCalibrationInvalid()
    {
    }

    @Override
    public void onCalibrationFailed(String reason)
    {
    }
    private void log()
    {
        runOnUiThread(new Runnable()
        {
            @Override
            public void run()
            {

            }
        });
    }

}

And here is my complete XML code "indoor_positioning.xml":

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:weightSum="1">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/idk"
        android:scaleType="fitCenter" />
    <ImageView
        android:id="@+id/bluedottwo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/bluedottwo"
        android:contentDescription="@string/bd"/>-->

</LinearLayout>

In the Java code, I do not know what to assign and how to access those two variables called "floorPlan" and "mFloorPlan". I am also not sure if my XML code is correct, as I am not sure should I have customView or those two ImageViews? I do not know how to properly use the "Util" class (as it is mainly used for scaling the image/bitmap I guess), and other methods as you can see I have left them empty. The Java code is not complete, and that is why if somebody knows the answers and also knows what is missing inside the codes, that would be great. If you have been working with IndoorAtlas SDk or have worked with this SDK or an expert in using bitmaps, and if you know the answer, then please correct my Java program by pointing out the mistakes and by showing the correct codes.

Here is image link which shows the actual result I am getting on my android phone: "https://i.sstatic.net/WPsN4.png"

As you can see in the image that the floor plan is loaded and being shown on the top left hand corner of the screen with the blue dot inside. When I run the application on my phone, firstly, the blue dot loads, after that, floor plan bitmap is loaded and the blue dot goes below the floor plan image/bitmap and after that, the blue dot comes inside the floor plan. But I want to see the floor plan image in the full screen, with rotation, and both the floor plan image and the blue dot should be loaded at the same time.

Many thanks in advance.


Solution

  • To answer your first question: what to do with variables floorPlan and mFloorPlan? This IAFloorPlan object holds the metadata of the floor plan. E.g. it's pixel dimensions and information how to convert metric positions to pixel positions etc. You only need one so drop the floorPlan variable.

    Secondly, when ever you draw positions on you image, you need to scale the image point's I & J relative to you image on screen. E.g: if the service returns pixel position 1024,1024 which is relative to the original floor plan bitmap that was say 2048,2048, then you location is at center of the map. If the bitmap in your application's UI has e.g. size 1024x1024 (half of the original) the you cannot draw to the same coordinates returned by the service (1024,1024) but you need to count in the scale which here is 0.5, hence the coordinates to draw into would become:

    x = 1024(originalX) * 0.5(scale) => 512
    y = 1024(orginalY) * 0.5(scale) => 512
    

    You should also make sure that the original aspect ratio of the floor plan is always preserved. Also, when calculating X&Y where to draw, take into account possible padding and if image is smaller that view's canvas you need to check how it is positioned (e.g. centered).

    But as I added in comments to your question: I would recommend using v2.0 of IndoorAtlas Android SDK with better documentation and examples. Docs here: http://docs.indooratlas.com/android/, examples: https://github.com/IndoorAtlas/android-sdk-examples.