// Copyright 2010 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.google.android.stardroid.layers; import com.google.android.stardroid.R; import com.google.android.stardroid.base.Closeables; import com.google.android.stardroid.base.Lists; import com.google.android.stardroid.base.TimeConstants; import com.google.android.stardroid.control.AstronomerModel; import com.google.android.stardroid.provider.ephemeris.OrbitalElements; import com.google.android.stardroid.renderer.RendererObjectManager.UpdateType; import com.google.android.stardroid.source.AbstractAstronomicalSource; import com.google.android.stardroid.source.AstronomicalSource; import com.google.android.stardroid.source.PointSource; import com.google.android.stardroid.source.Sources; import com.google.android.stardroid.source.TextSource; import com.google.android.stardroid.source.impl.PointSourceImpl; import com.google.android.stardroid.source.impl.TextSourceImpl; import com.google.android.stardroid.units.GeocentricCoordinates; import com.google.android.stardroid.util.Blog; import com.google.android.stardroid.util.MiscUtil; import android.content.res.Resources; import android.graphics.Color; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Date; import java.util.EnumSet; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * @author Brent Bryan */ public class IssLayer extends AbstractSourceLayer { private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final AstronomerModel model; private IssSource issSource; public IssLayer(Resources resources, AstronomerModel model) { super(resources, true); this.model = model; } @Override protected void initializeAstroSources(ArrayList<AstronomicalSource> sources) { this.issSource = new IssSource(model, getResources()); sources.add(issSource); scheduler.scheduleAtFixedRate( new OrbitalElementsGrabber(issSource), 0, 60, TimeUnit.SECONDS); } @Override public int getLayerId() { return -110; } @Override protected int getLayerNameId() { // TODO(brent): Update to different preference return R.string.show_hubble_layer_pref; } /** Thread Runnable which parses the orbital elements out of the Url. */ static class OrbitalElementsGrabber implements Runnable { private static final long UPDATE_FREQ_MS = TimeConstants.MILLISECONDS_PER_HOUR; private static final String TAG = MiscUtil.getTag(OrbitalElementsGrabber.class); private static final String URL_STRING = "http://spaceflight.nasa.gov/realdata/" + "sightings/SSapplications/Post/JavaSSOP/orbit/ISS/SVPOST.html"; private final IssSource source; private long lastSuccessfulUpdateMs = -1L; public OrbitalElementsGrabber(IssSource source) { this.source = source; } /** * Parses the OrbitalElements from the given BufferedReader. Factored out * of {@link #getOrbitalElements} to simplify testing. */ OrbitalElements parseOrbitalElements(BufferedReader in) throws IOException { String s; while ((s = in.readLine()) != null && !s.contains("M50 Keplerian")) {} // Skip the dashed line in.readLine(); float[] params = new float[9]; int i = 0; for (; i < params.length && (s = in.readLine()) != null; i++) { s = s.substring(46).trim(); String[] tokens = s.split("\\s+"); params[i] = Float.parseFloat(tokens[2]); } if (i == params.length) { // we read all the data. // TODO(serafini): Add magic here to create orbital elements or whatever. StringBuilder sb = new StringBuilder(); for (int pi = 0; pi < params.length; pi++) { sb.append(" " + params[pi]); } Blog.d(this, "Params: " + sb); } return null; } /** * Reads the given URL and returns the OrbitalElements associated with the object * described therein. */ OrbitalElements getOrbitalElements(String urlString) { BufferedReader in = null; try { URLConnection connection = new URL(urlString).openConnection(); in = new BufferedReader(new InputStreamReader(connection.getInputStream())); return parseOrbitalElements(in); } catch (IOException e) { Log.e(TAG, "Error reading Orbital Elements"); } finally { Closeables.closeSilently(in); } return null; } @Override public void run() { long currentTimeMs = System.currentTimeMillis(); if ((currentTimeMs - lastSuccessfulUpdateMs) > UPDATE_FREQ_MS) { Blog.d(this, "Fetching ISS data..."); OrbitalElements elements = getOrbitalElements(URL_STRING); if (elements == null) { Log.d(TAG, "Error downloading ISS orbital data"); } else { lastSuccessfulUpdateMs = currentTimeMs; source.setOrbitalElements(elements); } } } } /** AstronomicalSource corresponding to the International Space Station. */ static class IssSource extends AbstractAstronomicalSource { private static final long UPDATE_FREQ_MS = 1L * TimeConstants.MILLISECONDS_PER_SECOND; private static final int ISS_COLOR = Color.YELLOW; private final GeocentricCoordinates coords = new GeocentricCoordinates(1f, 0f, 0f); private final ArrayList<PointSource> pointSources = new ArrayList<PointSource>(); private final ArrayList<TextSource> textSources = new ArrayList<TextSource>(); private final AstronomerModel model; private final String name; private OrbitalElements orbitalElements = null; private boolean orbitalElementsChanged; private long lastUpdateTimeMs = 0L; public IssSource(AstronomerModel model, Resources resources) { this.model = model; this.name = resources.getString(R.string.space_station); pointSources.add(new PointSourceImpl(coords, ISS_COLOR, 5)); textSources.add(new TextSourceImpl(coords, name, ISS_COLOR)); } public synchronized void setOrbitalElements(OrbitalElements elements) { this.orbitalElements = elements; orbitalElementsChanged = true; } @Override public List<String> getNames() { return Lists.asList(name); } @Override public GeocentricCoordinates getSearchLocation() { return coords; } private void updateCoords(Date time) { lastUpdateTimeMs = time.getTime(); orbitalElementsChanged = false; if (orbitalElements == null) { return; } // TODO(serafini): Update coords of Iss from OrbitalElements. // issCoords.assign(...); } @Override public Sources initialize() { updateCoords(model.getTime()); return this; } @Override public synchronized EnumSet<UpdateType> update() { EnumSet<UpdateType> updateTypes = EnumSet.noneOf(UpdateType.class); Date modelTime = model.getTime(); if (orbitalElementsChanged || Math.abs(modelTime.getTime() - lastUpdateTimeMs) > UPDATE_FREQ_MS) { updateCoords(modelTime); if (orbitalElements != null) { updateTypes.add(UpdateType.UpdatePositions); } } return updateTypes; } @Override public List<? extends TextSource> getLabels() { return textSources; } @Override public List<? extends PointSource> getPoints() { return pointSources; } } }