Repository: mvglasow/satstat
Branch: master
Commit: 86fb47814d02
Files: 191
Total size: 891.4 KB
Directory structure:
gitextract_u1yto3lt/
├── .classpath
├── .externalToolBuilders/
│ └── Set Build Info.launch
├── .gitignore
├── .project
├── .settings/
│ └── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── LICENSE
├── README.md
├── build.gradle
├── libs/
│ ├── android-support-v4.jar
│ ├── androidsvg-1.2.2-beta-1.jar
│ ├── kxml2-2.3.0.jar
│ ├── mapsforge-core-0.9.1.jar
│ ├── mapsforge-map-0.9.1.jar
│ ├── mapsforge-map-android-0.9.1.jar
│ ├── mapsforge-map-reader-0.9.1.jar
│ └── mapsforge-themes-0.9.1.jar
├── metadata/
│ └── en-US/
│ └── changelogs/
│ ├── 10.txt
│ ├── 20.txt
│ ├── 2000.txt
│ ├── 2010.txt
│ ├── 2020.txt
│ ├── 30.txt
│ ├── 3000.txt
│ ├── 3010.txt
│ ├── 3020.txt
│ ├── 3030.txt
│ ├── 40.txt
│ ├── 50.txt
│ ├── 60.txt
│ ├── 70.txt
│ └── 80.txt
├── mkpng
├── proguard-project.txt
├── project.properties
├── res/
│ ├── drawable/
│ │ ├── divider.xml
│ │ ├── ic_stat_notify_nolocation.xml
│ │ ├── list_selector_background.xml
│ │ ├── list_selector_background_transition.xml
│ │ ├── zoom_control_in.xml
│ │ └── zoom_control_out.xml
│ ├── layout/
│ │ ├── activity_about.xml
│ │ ├── activity_legend.xml
│ │ ├── activity_main.xml
│ │ ├── activity_map_download.xml
│ │ ├── activity_settings.xml
│ │ ├── alert_map_path.xml
│ │ ├── download_list_item.xml
│ │ ├── fragment_main_dummy.xml
│ │ ├── fragment_main_gps.xml
│ │ ├── fragment_main_map.xml
│ │ ├── fragment_main_radio.xml
│ │ ├── fragment_main_sensors.xml
│ │ ├── ril_cdma_list_item.xml
│ │ ├── ril_list_item.xml
│ │ └── tree_list_item_wrapper.xml
│ ├── layout-land/
│ │ ├── fragment_main_gps.xml
│ │ └── fragment_main_sensors.xml
│ ├── layout-w640dp/
│ │ └── fragment_main_radio.xml
│ ├── menu/
│ │ └── main.xml
│ ├── raw/
│ │ └── .gitignore
│ ├── values/
│ │ ├── attrs.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ ├── styles.xml
│ │ └── treeview_styles.xml
│ ├── values-ca/
│ │ └── strings.xml
│ ├── values-de/
│ │ └── strings.xml
│ ├── values-es/
│ │ └── strings.xml
│ ├── values-fr/
│ │ └── strings.xml
│ ├── values-hu/
│ │ └── strings.xml
│ ├── values-it/
│ │ └── strings.xml
│ ├── values-lt/
│ │ └── strings.xml
│ ├── values-pt-rBR/
│ │ └── strings.xml
│ ├── values-sw600dp/
│ │ └── dimens.xml
│ ├── values-sw720dp-land/
│ │ └── dimens.xml
│ ├── values-v21/
│ │ └── styles.xml
│ └── xml/
│ └── preferences.xml
├── setbuild.sh
└── src/
├── com/
│ ├── hzi/
│ │ └── UTM.java
│ └── vonglasow/
│ └── michael/
│ └── satstat/
│ ├── Const.java
│ ├── GpsEventReceiver.java
│ ├── PasvLocListenerService.java
│ ├── SatStatApplication.java
│ ├── data/
│ │ ├── CellTower.java
│ │ ├── CellTowerCdma.java
│ │ ├── CellTowerGsm.java
│ │ ├── CellTowerList.java
│ │ ├── CellTowerListCdma.java
│ │ ├── CellTowerListGsm.java
│ │ ├── CellTowerListLte.java
│ │ └── CellTowerLte.java
│ ├── ui/
│ │ ├── AboutActivity.java
│ │ ├── GpsSectionFragment.java
│ │ ├── LegendActivity.java
│ │ ├── MainActivity.java
│ │ ├── MapDownloadActivity.java
│ │ ├── MapSectionFragment.java
│ │ ├── RadioSectionFragment.java
│ │ ├── SensorSectionFragment.java
│ │ └── SettingsActivity.java
│ ├── utils/
│ │ ├── DownloadTreeViewAdapter.java
│ │ ├── HttpDownloader.java
│ │ ├── PermissionHelper.java
│ │ ├── RemoteDirListListener.java
│ │ ├── RemoteDirListTask.java
│ │ ├── RemoteFile.java
│ │ ├── RemoteFileComparator.java
│ │ ├── WifiCapabilities.java
│ │ └── WifiScanResultComparator.java
│ └── widgets/
│ ├── GpsSnrView.java
│ ├── GpsStatusView.java
│ ├── LocProviderPreference.java
│ ├── MapViewPager.java
│ ├── NetworkTypePreference.java
│ └── SquareView.java
├── pl/
│ └── polidea/
│ └── treeview/
│ ├── AbstractTreeViewAdapter.java
│ ├── DownloadTreeStateManager.java
│ ├── InMemoryTreeNode.java
│ ├── InMemoryTreeStateManager.java
│ ├── NodeAlreadyInTreeException.java
│ ├── NodeNotInTreeException.java
│ ├── TreeBuilder.java
│ ├── TreeConfigurationException.java
│ ├── TreeNodeInfo.java
│ ├── TreeStateManager.java
│ ├── TreeViewList.java
│ ├── overview.html
│ └── package-info.java
└── uk/
└── me/
└── jstott/
└── jcoord/
├── CoordinateSystem.java
├── ECEFRef.java
├── IrishRef.java
├── LatLng.java
├── MGRSRef.java
├── NotDefinedOnUTMGridException.java
├── OSRef.java
├── RefEll.java
├── Test.java
├── UTMRef.java
├── Util.java
├── datum/
│ ├── Datum.java
│ ├── ETRF89Datum.java
│ ├── Ireland1965Datum.java
│ ├── OSGB36Datum.java
│ ├── WGS84Datum.java
│ └── nad27/
│ ├── NAD27AlaskaDatum.java
│ ├── NAD27AlbertaBritishColumbiaDatum.java
│ ├── NAD27AleutianEastDatum.java
│ ├── NAD27AleutianWestDatum.java
│ ├── NAD27BahamasDatum.java
│ ├── NAD27CanadaDatum.java
│ ├── NAD27CanadaEastDatum.java
│ ├── NAD27CanadaManitobaOntarioDatum.java
│ ├── NAD27CanadaNWTerritoryDatum.java
│ ├── NAD27CanadaYukonDatum.java
│ ├── NAD27CanalZoneDatum.java
│ ├── NAD27CaribbeanDatum.java
│ ├── NAD27CentralAmericaDatum.java
│ ├── NAD27ContiguousUSDatum.java
│ ├── NAD27CubaDatum.java
│ ├── NAD27EasternUSDatum.java
│ ├── NAD27GreenlandDatum.java
│ ├── NAD27MexicoDatum.java
│ ├── NAD27SanSalvadorDatum.java
│ └── NAD27WesternUSDatum.java
└── ellipsoid/
├── Airy1830Ellipsoid.java
├── AustralianNational1966Ellipsoid.java
├── Bessel1841Ellipsoid.java
├── Clarke1866Ellipsoid.java
├── Clarke1880Ellipsoid.java
├── Ellipsoid.java
├── EverestEllipsoid.java
├── Fischer1960Ellipsoid.java
├── Fischer1968Ellipsoid.java
├── GRS67Ellipsoid.java
├── GRS75Ellipsoid.java
├── GRS80Ellipsoid.java
├── Hayford1910Ellipsoid.java
├── Helmert1906Ellipsoid.java
├── Hough1956Ellipsoid.java
├── IERS1989Ellipsoid.java
├── InternationalEllipsoid.java
├── Krassovsky1940Ellipsoid.java
├── ModifiedAiryEllipsoid.java
├── ModifiedEverestEllipsoid.java
├── NewInternational1967Ellipsoid.java
├── SouthAmerican1969Ellipsoid.java
├── WGS60Ellipsoid.java
├── WGS66Ellipsoid.java
├── WGS72Ellipsoid.java
└── WGS84Ellipsoid.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .classpath
================================================
================================================
FILE: .externalToolBuilders/Set Build Info.launch
================================================
================================================
FILE: .gitignore
================================================
.gradle
bin
build
gen
res/raw/build.txt
================================================
FILE: .project
================================================
satstat
org.eclipse.ui.externaltools.ExternalToolBuilder
auto,full,incremental,
LaunchConfigHandle
<project>/.externalToolBuilders/Set Build Info.launch
org.eclipse.andmore.ResourceManagerBuilder
org.eclipse.andmore.PreCompilerBuilder
org.eclipse.jdt.core.javabuilder
org.eclipse.andmore.ApkBuilder
org.eclipse.andmore.AndroidNature
org.eclipse.jdt.core.javanature
================================================
FILE: .settings/org.eclipse.jdt.core.prefs
================================================
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
================================================
FILE: AndroidManifest.xml
================================================
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/}
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
{one line to give the program's name and a brief idea of what it does.}
Copyright (C) {year} {name of author}
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
satstat Copyright (C) 2013 mvglasow
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
{http://www.gnu.org/licenses/}.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
{http://www.gnu.org/philosophy/why-not-lgpl.html}.
================================================
FILE: README.md
================================================
SatStat
=======
Android Location, Sensor and Radio Network Status
---
================================================
FILE: build.gradle
================================================
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.1.2'
}
}
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion '25.0.0'
defaultConfig {
minSdkVersion 11
targetSdkVersion 23
}
packagingOptions {
exclude 'META-INF/LICENSE.txt'
exclude 'META-INF/NOTICE.txt'
}
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
lintOptions {
abortOnError false
}
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
compile 'com.android.support:support-v4:23.2.1'
compile 'com.android.support:appcompat-v7:23.2.1'
compile 'com.android.support:design:23.2.1'
compile 'commons-net:commons-net:3.5'
compile 'org.mapsforge:mapsforge-core:0.9.1'
compile 'org.mapsforge:mapsforge-map:0.9.1'
compile 'org.mapsforge:mapsforge-map-reader:0.9.1'
compile 'org.mapsforge:mapsforge-themes:0.9.1'
compile 'org.mapsforge:mapsforge-map-android:0.9.1'
compile 'com.caverock:androidsvg:1.2.2-beta-1'
compile 'net.sf.kxml:kxml2:2.3.0'
}
}
================================================
FILE: metadata/en-US/changelogs/10.txt
================================================
Support for legacy (3.0+) Android versions (#1)
WiFi now displays channels (#2)
Sky plot now aligned correctly when display is rotated (#4)
================================================
FILE: metadata/en-US/changelogs/20.txt
================================================
AGPS data download
Support for 5 GHz WiFI frequencies (#9)
Tab navigation in action bar in addition to swiping
Prevent NPE when returning to main activity
Improved sizing for sky plot to avoid pushing vies off the screen (#6)
================================================
FILE: metadata/en-US/changelogs/2000.txt
================================================
Map view: new view to visualize reported locations
GPS view: display SNR bars for all known satellites (including Beidou and various SBAS systems)
Radio cell view: add support for LTE on Android versions that support it
Radio cell view: display neighboring cells correctly
Radio cell view: use CellInfo on Android versions that support it
Radio cell view: eliminate duplicate entries
Radio cell view: show PSC for UMTS cells
Radio cell view: display dashes instead of bogus values when data is missing
Notifications: animate icon while searching for location
Notifications: show correct TTFF
Notifications: update content after losing fix
About screen: show version
When crashing, write crash log file
Fix cases in which automatic AGPS download would fail silently (#38)
Update mobile network type even when no data connection is active (#30)
Fix screen orientation lock issue on landscape-default devices (#32)
Do not crash upon receiving location updates with NaN coordinates (#35)
Do not crash when radio is not connected
================================================
FILE: metadata/en-US/changelogs/2010.txt
================================================
Add option for metric vs. Imperial measurement units
Add option for UTC vs. local time for GPS timestamps
Add option to split UMTS/LTE cell IDs
Add options for various coordinate formats
Add Hungarian translations, thanks to kadarivan
Add legend for colors and symbols used
Size graphics depending on display density
Increase expiration delay for location providers
Target API 19
Introduce Material LAF
Include notice in AGPS message that success cannot be reported
RIL: use all available cell information sources
Preserve state of main activity when returning from another
Run media scanner on crash log files
Use friendly date and time for crash log files
RIL: display neighboring cells correctly
RIL: show correct signal strength
RIL: correctly separate LTE cells from GSM/UMTS cells
Fix Mapsforge crash
Fix color issue in Sensor view on Marshmallow
================================================
FILE: metadata/en-US/changelogs/2020.txt
================================================
Fix crashes when permissions are denied
Fix memory leak when stopping or destroying main Activity
================================================
FILE: metadata/en-US/changelogs/30.txt
================================================
Fix regression from 60b8bec pushing sky plot off screen in landscape
Log satellites with near-zero elevation/azimuth
================================================
FILE: metadata/en-US/changelogs/3000.txt
================================================
Support for offline maps, which can be shared with other Mapsforge apps (#65)
Add option to show both UMTS/LTE cell ID representations (#77)
Optionally show speed in knots (#74)
Label GNSS systems in satellite SNR bar graph (#64)
Different options to sort WiFis (#44)
Allow manual purging of map tile cache
Switch map tile provider from (discontinued) Mapquest to OSM (#86)
Fully support Android's new runtime permission model (#67)
Update to Mapsforge 0.6.1, now with proper persistent caching
Look and feel improvements
Fix faulty mph conversion in satellite view
Fix memory leak when rotating or leaving and re-entering map view
================================================
FILE: metadata/en-US/changelogs/3010.txt
================================================
Fix crash in Settings on API levels prior to 19 (#89)
Update French translation (thanks eliovir)
Use correct unit for speed in notification
================================================
FILE: metadata/en-US/changelogs/3020.txt
================================================
Update Portuguese translation
Fix crash in CDMA cell view
Fix nautic coordinate display
================================================
FILE: metadata/en-US/changelogs/3030.txt
================================================
Sky plot: make compass orientation work on devices with compass but no orientation sensor
Sky plot: add support for Galileo
Sky plot and notification: add support for UTM coordinates
Preferences: include a hint on map download source
Map download: keep notification after map download finishes
Map download: adapt to new directory layout on Mapsforge mirror server
Metadata: add feature graphics, icon and screenshots for F-Droid
Translations: updates for French and Hungarian
Bug fixes:
Sky plot: use Altitude consistently
Sky plot and notification: display negative coordinates correctly in non-decimal formats
Map view: fix crash caused by a rare race condition
Map download: do not crash if retrieving the map directory fails
Map download: correctly display map sizes above 2 GiB
Map download: display list correctly even after screen rotation
About screen: make links clickable
================================================
FILE: metadata/en-US/changelogs/40.txt
================================================
GPS status notification whenever an app accesses GPS (configurable)
Add signal-to-noise graph for GPS/GLONASS satellites
Translations: Catalan, German, Spanish, French, Italian, Lithuanian
Add NESW marks to sky plot
Lock display rotation when device is in near-horizontal position
Use sensor resolution to determine number of decimals displayed
Show build number in About screen
Fix dormant sizing bug in sky plot
================================================
FILE: metadata/en-US/changelogs/50.txt
================================================
Update AGPS data when connecting to a WiFi network (configurable)
Prevent crash when sensors report zero resolution
================================================
FILE: metadata/en-US/changelogs/60.txt
================================================
Prevent crash on AGPS update
================================================
FILE: metadata/en-US/changelogs/70.txt
================================================
Show cellular network type (#21)
Show type of WiFi encryption (#20)
Auto-refresh WiFi network list every second (#26)
Touch WiFi network to pin it to the top of the list (#26)
Hide empty cell list in network view
Choose from all available networks for AGPS auto-update (#18)
Check for captive portal before AGPS update attempt (#18)
Repeat AGPS auto-update each time the interval expires if a selected network is available (#18)
Fix minor screen real estate issue in German translation (#23)
Prevent crash upon switching to other activities (#27)
================================================
FILE: metadata/en-US/changelogs/80.txt
================================================
Fix time conversion error that could cause an endless AGPS update loop (#31)
Typo in French translation
Show TTFF in notification (experimental)
================================================
FILE: mkpng
================================================
#!/bin/bash
## Generates PNG drawables from all SVG files.
SVG_PATH=./extra
LDPI_PATH=./res/drawable-ldpi
MDPI_PATH=./res/drawable-mdpi
HDPI_PATH=./res/drawable-hdpi
XHDPI_PATH=./res/drawable-xhdpi
XXHDPI_PATH=./res/drawable-xxhdpi
# Android density bins are:
# LDPI: 120 dpi
# MDPI: 160 dpi
# HDPI: 240 dpi
# XHDPI: 320 dpi
# XXHDPI: 480 dpi
# Default for icons is 1 SVG user unit ("px") = 1 dp.
# However, both these units have their own conflicting definitions:
# - 1 SVG user units = 1 pixel at 90 dpi.
# - 1 dp = 1 pixel at MDPI (160 dpi).
# Therefore, SVG export needs to use the following dpi values:
LDPI_RES=67.5
MDPI_RES=90
HDPI_RES=135
XHDPI_RES=180
XXHDPI_RES=270
## Creates a single PNG file from a given input SVG file at the given DPI resolution.
##
## If the output file exists and is newer than the input file, no action is taken.
##
## @param $1 The input SVG filename (full path)
## @param $2 The output PNG filename (full path)
## @param $3 The resolution in DPI
function make_png_dpi {
if [[ -e $2 && $2 -nt $1 ]] ; then
echo Skipping $1 because $2 exists and is newer
else
echo inkscape --without-gui --export-area-page --export-dpi=$3 --export-png=$2 $1
inkscape --without-gui --export-area-page --export-dpi=$3 --export-png=$2 $1
fi
}
## Creates a single PNG file from a given input SVG file at the given width.
##
## If the output file exists and is newer than the input file, no action is taken.
##
## @param $1 The input SVG filename (full path)
## @param $2 The output PNG filename (full path)
## @param $3 The width in pixels
function make_png_width {
if [[ -e $2 && $2 -nt $1 ]] ; then
echo Skipping $1 because $2 exists and is newer
else
echo inkscape --without-gui --export-area-page --export-width=$3 --export-png=$2 $1
inkscape --without-gui --export-area-page --export-width=$3 --export-png=$2 $1
fi
}
## Converts a SVG icon to PNG drawables using the default of 1 px = 1 dip.
##
## @param $1 The input SVG filename (path stripped)
## @param $2 The output PNG filename (path stripped)
function convert_by_dpi {
baresvgfile=$1
barepngfile=$2
make_png_dpi $SVG_PATH/$baresvgfile $LDPI_PATH/$barepngfile $LDPI_RES
make_png_dpi $SVG_PATH/$baresvgfile $MDPI_PATH/$barepngfile $MDPI_RES
make_png_dpi $SVG_PATH/$baresvgfile $HDPI_PATH/$barepngfile $HDPI_RES
make_png_dpi $SVG_PATH/$baresvgfile $XHDPI_PATH/$barepngfile $XHDPI_RES
make_png_dpi $SVG_PATH/$baresvgfile $XXHDPI_PATH/$barepngfile $XXHDPI_RES
}
## Converts a SVG icon to PNG drawables using a specified width.
##
## @param $1 The input SVG filename (path stripped)
## @param $2 The output PNG filename (path stripped)
## @param $3 The width for the converted PNG files, in dip
function convert_by_width {
baresvgfile=$1
barepngfile=$2
width_mdpi=$3
# Derive other sizes from MDPI
# Ordered to do lossy operations (integer division) last
width_xhdpi=$(($width_mdpi * 2))
width_xxhdpi=$(($width_mdpi * 3))
width_hdpi=$(($width_xxhdpi / 2))
width_ldpi=$(($width_hdpi / 2))
make_png_width $SVG_PATH/$baresvgfile $LDPI_PATH/$barepngfile $width_ldpi
make_png_width $SVG_PATH/$baresvgfile $MDPI_PATH/$barepngfile $width_mdpi
make_png_width $SVG_PATH/$baresvgfile $HDPI_PATH/$barepngfile $width_hdpi
make_png_width $SVG_PATH/$baresvgfile $XHDPI_PATH/$barepngfile $width_xhdpi
make_png_width $SVG_PATH/$baresvgfile $XXHDPI_PATH/$barepngfile $width_xxhdpi
}
for svgfile in $SVG_PATH/*.svg
do
# strip path from file name
baresvgfile=${svgfile#$SVG_PATH/}
barepngfile=${baresvgfile%.svg}.png
case $baresvgfile in
ic_context_marker_*.svg)
convert_by_width $baresvgfile $barepngfile 32
;;
ic_launcher.svg|ic_launcher_oi_filemanager.svg)
convert_by_width $baresvgfile $barepngfile 48
;;
*)
convert_by_dpi $baresvgfile $barepngfile
esac
done
================================================
FILE: proguard-project.txt
================================================
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: project.properties
================================================
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must be checked in Version Control Systems.
#
# To customize properties used by the Ant build system edit
# "ant.properties", and override values to adapt the script to your
# project structure.
#
# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-23
android.library=false
android.library.reference.1=../../workspaces/satstat/libprojects/appcompat
android.library.reference.2=../../workspaces/satstat/libprojects/design
================================================
FILE: res/drawable/divider.xml
================================================
================================================
FILE: res/drawable/ic_stat_notify_nolocation.xml
================================================
================================================
FILE: res/drawable/list_selector_background.xml
================================================
================================================
FILE: res/drawable/list_selector_background_transition.xml
================================================
================================================
FILE: res/drawable/zoom_control_in.xml
================================================
================================================
FILE: res/drawable/zoom_control_out.xml
================================================
================================================
FILE: res/layout/activity_about.xml
================================================
================================================
FILE: res/layout/activity_legend.xml
================================================
================================================
FILE: res/layout/activity_main.xml
================================================
================================================
FILE: res/layout/activity_map_download.xml
================================================
================================================
FILE: res/layout/activity_settings.xml
================================================
================================================
FILE: res/layout/alert_map_path.xml
================================================
================================================
FILE: res/layout/download_list_item.xml
================================================
================================================
FILE: res/layout/fragment_main_dummy.xml
================================================
================================================
FILE: res/layout/fragment_main_gps.xml
================================================
================================================
FILE: res/layout/fragment_main_map.xml
================================================
================================================
FILE: res/layout/fragment_main_radio.xml
================================================
================================================
FILE: res/layout/fragment_main_sensors.xml
================================================
================================================
FILE: res/layout/ril_cdma_list_item.xml
================================================
================================================
FILE: res/layout/ril_list_item.xml
================================================
================================================
FILE: res/layout/tree_list_item_wrapper.xml
================================================
================================================
FILE: res/layout-land/fragment_main_gps.xml
================================================
================================================
FILE: res/layout-land/fragment_main_sensors.xml
================================================
================================================
FILE: res/layout-w640dp/fragment_main_radio.xml
================================================
================================================
FILE: res/menu/main.xml
================================================
================================================
FILE: res/raw/.gitignore
================================================
================================================
FILE: res/values/attrs.xml
================================================
================================================
FILE: res/values/dimens.xml
================================================
16dp
16dp
================================================
FILE: res/values/strings.xml
================================================
SatStat
Settings
Record
Stop recording
Reload AGPS data
Legend
About
■
◼
■
Error writing to file
External storage not available
Started recording
Recording sensor data
Touch to stop recording
AGPS data reload requested – note that Android does not report success or failure
Can\'t refresh data – launch browser, sign into the network and try again
No network available, can\'t reload AGPS data
GPS
Sensors
Radio
Map
Latitude
Longitude
Coordinates
Decl.
Speed
Altitude
Last Fix Obtained
Bearing
Orientation
Error
Satellites
TTFF (s)
–
N
NNE
NE
ENE
E
ESE
SE
SSE
S
SSW
SW
WSW
W
WNW
NW
NNW
Acceleration and Gravity
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotation
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientation
Azimuth
Orientation
Pitch
Roll
Magnetic Field
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorology
°C
hPa
Relative Humidity (%)
Miscellaneous
lux
Proximity (cm)
GSM
MCC
MNC
Cell ID
LAC
PSC
ASU
CDMA
SID
NID
BSID
LTE
TAC
PCI
WiFi
BSSID (MAC)
Ch
dBm
About
Location, Sensor and Radio Network Status
Version
<p>
If something doesn\'t work the way it should, or if you have an idea to make this
app even greater, please report it at:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p><p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p><p>
You should have received a copy of the GNU General Public License
along with this program. If not, see
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
The source code for this application is at:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
mph
kn
m
ft
GPS fix notification
Notify me when an application gets my location from GPS
GPS search notification
Notify me when an application is searching for my location
Settings
Searching for location
Touch to view status
Notifications
AGPS Data Update
On WiFi connect
Refresh AGPS data when my device connects to a WiFi network
Update frequency
%s
- Every time
- Once a day
- Once in 2 days
- Once in 3 days
- Once in 4 days
- Once in 5 days
- Once in 6 days
- Once a week
- 0
- 1
- 2
- 3
- 4
- 5
- 6
- 7
Last update
%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
Networks
Refresh AGPS data when my device connects to one of the selected networks
Map
Location Sources
Show selected location sources on the map
Data display
Use metric units
If unchecked, Imperial units will be used (except in sensor view)
Show speed in knots
If unchecked, metric or imperial units (as selected above) will be used
Coordinates
- Decimal degrees
- Degrees, minutes
- Degrees, minutes, seconds
- MGRS
- UTM
- 0
- 1
- 2
- 3
- 4
Display GPS time in UTC
Display GPS fix time in UTC rather than in local time
Split UMTS/LTE cell IDs
Split cell IDs into RNCID/CID or eNodeB/sector ID
© OpenStreetMap contributors
Legend
GPS
Satellite used in fix
Satellite not used in fix
Sensors
High accuracy
Medium accuracy
Low accuracy
Unreliable
Radio
2G cell
3G cell
4G cell
Open WiFi
WEP security
WPA-PSK (personal) security
WPA-EAP (enterprise) security
Ad-hoc WiFi
Unknown WiFi type
Map
Location from %s provider
Stale location from any provider
GPS
SBAS
55–64
GLONASS
97–192
QZSS
Beidou
236–300
Galileo
Sort WiFi networks
- By BSSID
- By name
- By channel
- By signal strength
- 0
- 1
- 2
- 3
Use offline map
Render maps on the device (requires downloading an offline map)
Offline map folder
Path to offline map folder:
To browse for the offline map folder, you need to install a supported file manager.
CM File Manager
OI File Manager
OK
Cancel
Download offline map
Get maps from Mapsforge
Clear map tile cache
Force all tiles to be fetched or rendered again, useful for troubleshooting
Map tile cache cleared
Download offline map
Folder is empty
A download for a map of the same name is already in progress
It seems you already have an up-to-date copy of this map. Do you want to download it anyway?
Yes
No
All downloads have completed
To download offline maps, you must grant permission to access files
No location or radio information will be shown unless you grant permission to access the device\'s location
To refresh AGPS data, you must grant permission to access the device\'s location
To use offline maps, you must grant permission to access files
SatStat is requesting permissions
Touch to grant or deny
Display both cell ID formats
Show both split and non-split forms, with the one selected above appearing on top
Position outside UTM range
An error occurred while trying to retrieve the list of maps from the server
Retry
================================================
FILE: res/values/styles.xml
================================================
#FF9800
#80CBC4
#F44336
#8BC344
#FF9800
#F44336
#9C27B0
#8BC344
#80CBC4
#9C27B0
#FF2196F3
#4D2196F3
#FF9C27B0
#4D9C27B0
#FF8BC344
#4D8BC344
#FFFF9800
#4DFF9800
#FFF44336
#4DF44336
#FF9E9E9E
#4D9E9E9E
- @drawable/ic_context_marker_blue
- @color/circle_blue_stroke
- @color/circle_blue_fill
- @drawable/ic_context_marker_purple
- @color/circle_purple_stroke
- @color/circle_purple_fill
- @drawable/ic_context_marker_green
- @color/circle_green_stroke
- @color/circle_green_fill
- @drawable/ic_context_marker_orange
- @color/circle_orange_stroke
- @color/circle_orange_fill
- @drawable/ic_context_marker_red
- @color/circle_red_stroke
- @color/circle_red_fill
- @drawable/ic_context_marker_gray
- @color/circle_gray_stroke
- @color/circle_gray_fill
#B2FFFFFF
2dp
36dp
================================================
FILE: res/values/treeview_styles.xml
================================================
================================================
FILE: res/values-ca/strings.xml
================================================
SatStat
Configuració
Registra
Acaba registració
Actualitza dades AGPS
Llegenda
Quant a l\'aplicació
Error escrivint fitxer
Emmagatzematge extern no disponible
Registració començada
Registració dades
Toca si vols acabar la registració
Actualització dades AGPS sol·lecitada – ten present que Android no informa de l\'èxit de l\'operació
Impossible actualitzar dades – inicia el navegador, inicia la sessió a la xarxa i torna-ho a provar
Cap xarxa disponible, impossible actualitzar dades AGPS
GPS
Sensors
Xarxes
Latitud
Longitud
Coordenades
Decl.
Velocitat
Altitud
Última ubicació rebuda
Rumb
Orientació
Error
Satèl·lits
TTFF (s)
–
N
NNE
NE
ENE
E
ESE
SE
SSE
S
SSO
SO
OSO
O
ONO
NO
NNO
Acceleració i gravetat
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotació
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientació
Azimut
Orientació
Capcin.
Balanc.
Camp magnètic
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorologia
°C
hPa
Humitat relativa (%)
Miscel·lani
lux
Proximitat (cm)
GSM
MCC
MNC
Cell ID
LAC
ASU
CDMA
SID
NID
BSID
WiFi
BSSID (MAC)
C.
dBm
Quant a l\'aplicació
Location, Sensor and Radio Network Status
Versió
<p>
Si quelcom no funciona com es deuria,
o si tens una idea com millorar encara aquesta aplicació,
per a favor senyala-hi a:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p><p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p><p>
You should have received a copy of the GNU General Public License
along with this program. If not, see
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
El codi font d\'aquesta aplicació es troba a:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
m
Notificació d\'ubicació GPS
Notifica\'m quan una aplicació obtingui la meva ubicació desde GPS
Notificació de recerca GPS
Notifica\'m quan una aplicació cerqui la meva ubicació
Configuració
S\'està cercant l\'ubicació
Toca per veure l\'estat
Notificaciones
Actualització dades AGPS
En connexion WiFi
Actualitza dades AGPS quan el meu dispositiu es connecta amb una xarxa WiFi
Freqüència
- Cada cop
- Un cop cada dia
- Un cop cada 2 dies
- Un cop cada 3 dies
- Un cop cada 4 dies
- Un cop cada 5 dies
- Un cop cada 6 dies
- Un cop cada setmana
Última actualització
Xarxes
Actualitza dades AGPS quan el meu dispositiu es connecta amb una de les xarxes seleccionades
Mapa
Fonts d\'ubicació
Mostra les fonts d\'ubicació seleccionades al mapa
Representació de dades
Utilitza unitats mètriques
Mostra velocitat en nusos
Coordenades
- Graus
- Graus i minuts
- Graus, minuts i segons
- MGRS
- UTM
Mostra temps GPS en UTC
Utilitza UTC en comptes de temps local per mostrar el temps GPS
Subdivideix l\'ID de cel·les UMTS/LTE
Subdivideix l\'ID de cel·les en RNCID/CID o eNodeB/sector ID
Llegenda
GPS
Satèl·lite utilitzat per l\'ubicació
Satèl·lite no utilitzat per l\'ubicació
Sensors
Exactitud alta
Exactitud mitjana
Exactitud baixa
Xarxes
Cel·la 2G
Cel·la 3G
Cel·la 4G
WiFi obert
Seguretat WEP
Seguretat WPA-PSK (personal)
Seguretat WPA-EAP (enterprise)
WiFi ad hoc
Tipus de WiFi desconegut
Mapa
Ubicació de la font %s
Ubicació gastada de qualsevol font
Ordena les xarxes WiFi
- Per BSSID
- Per nom
- Per canal
- Per nivell del senyal
Utilitza mapa offline
Renderitza les mapes en el dispositiu (es necessita baixar una mapa offline)
Carpeta per a les mapes offline
Ruta a la carpeta per a les mapes offline:
Per poder seleccionar la carpeta, es necessita instal·lar un gestor d\'arxius suportat.
D\'acord
Cancel·la
Baixar mapa offline
Baixar mapes desde Mapsforge
Esborrar les teseles a memòria cau
Todes les teseles seran baixades o renderitzades de nou, útil en cas de problemes
Les teseles a memòria cau han estat esborrades
Baixar mapa offline
La carpeta està buida
S\'està ja baixant una mapa del meteix nom
Sembla que ja tens una còpia actual d\'aquesta mapa. Vols baixar-la tanmateix?
Sí
No
Totes les baixades s\'han completades
Per baixar mapes offline, cal permetre l\'accés a fitxers
Per veure ubicació o xarxes, cal permetre l\'accés a l\'ubicació del dispositiu
Per actualitzar dades AGPS, cal permetre l\'accés a l\'ubicació del dispositiu
Per utilitzar mapes offline, cal permetre l\'accés a fitxers
SatStat necessita permisos
Toca per permetre o denegar
Mostra tots dos formats de l\'ID de cel·la
================================================
FILE: res/values-de/strings.xml
================================================
SatStat
Einstellungen
Aufzeichnen
Aufzeichnung beenden
AGPS-Daten neu laden
Legende
Info
Fehler beim Schreiben in Datei
Externer Speicher nicht verfügbar
Aufzeichnung gestartet
Sensordaten werden aufgezeichnet
Zum Beenden antippen
Aktualisierung der AGPS-Daten angefordert – beachten Sie, dass Android den Erfolg der Aktualisierung nicht meldet
Daten können nicht aktualisiert werden – starten Sie den Browser, melden Sie sich am Netzwerk an und versuchen Sie es erneut
Kein Netz verfügbar, AGPS-Daten können nicht aktualisiert werden
GPS
Sensoren
Funknetze
Karte
Breite
Länge
Koordinaten
Dekl.
Geschw.
Höhe
Letzte Positionsbestimmung
Kurs
Orientation
Fehler
Satelliten
TTFF (s)
–
N
NNO
NO
ONO
O
OSO
SO
SSO
S
SSW
SW
WSW
W
WNW
NW
NNW
Beschleunigung/Schwerkraft
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotation
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Lage
Azimut
Orientation
Nick
Roll
Magnetfeld
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorologie
°C
hPa
Rel. Luftfeuchte (%)
Verschiedenes
lux
Abstand (cm)
GSM
MCC
MNC
Cell ID
LAC
PSC
ASU
CDMA
SID
NID
BSID
LTE
TAC
PCI
WiFi
BSSID (MAC)
K.
dBm
Info
Status für Standort, Sensoren und Netzwerk
Version
<p>
Wenn etwas nicht so funktioniert, wie es sollte, oder
wenn Sie Verbesserungsvorschläge haben, melden Sie diese bitte unter:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
Dieses Programm ist Freie Software: Sie können es unter den Bedingungen
der GNU General Public License, wie von der Free Software Foundation,
Version 3 der Lizenz oder (nach Ihrer Wahl) jeder neueren
veröffentlichten Version, weiterverbreiten und/oder modifizieren.
</p><p>
Dieses Programm wird in der Hoffnung, dass es nützlich sein wird, aber
OHNE JEDE GEWÄHRLEISTUNG, bereitgestellt; sogar ohne die implizite
Gewährleistung der MARKTFÄHIGKEIT oder EIGNUNG FÜR EINEN BESTIMMTEN ZWECK.
Siehe die GNU General Public License für weitere Details.
</p><p>
Sie sollten eine Kopie der GNU General Public License zusammen mit diesem
Programm erhalten haben. Wenn nicht, siehe
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
Der Quellcode für diese Applikation ist unter:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
kn
m
GPS-Standorthinweis
Benachrichtigen, wenn eine Applikation meinen Standort mit GPS bestimmt hat
Hinweis bei GPS-Suche
Benachrichtigen, wenn eine Applikation versucht, meinen Standort zu bestimmen
Einstellungen
Standort wird gesucht
Antippen, um Status zu sehen
Benachrichtigungen
AGPS-Aktualisierung
Bei WLAN-Verbindung
AGPS-Daten aktualisieren, wenn sich mein Gerät mit einem WLAN-Netzwerk verbindet
Aktualisierungsintervall
- Jedes Mal
- Einmal täglich
- Einmal alle 2 Tage
- Einmal alle 3 Tage
- Einmal alle 4 Tage
- Einmal alle 5 Tage
- Einmal alle 6 Tage
- Einmal pro Woche
Letzte Aktualisierung
%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
Netze
AGPS-Daten aktualisieren, wenn sich mein Gerät mit einem der ausgewählten Netzwerke verbindet
Karte
Standortquellen
Ausgewählte Standortquellen auf der Karte anzeigen
Datenanzeige
Metrische Einheiten verwenden
Wenn abgewählt, werden amerikanische Maßeinheiten verwendet (Ausnahme Sensoransicht)
Geschwindigkeit in Knoten anzeigen
Wenn abgewählt, werden metrische oder amerikanische Maßeinheiten verwendet (wie oben ausgewählt)
Koordinaten
- Grad (dezimal)
- Grad, Minuten
- Grad, Minuten, Sekunden
- MGRS
- UTM
GPS-Zeit in UTC anzeigen
GPS-Zeit in UTC statt in Ortszeit anzeigen
UMTS/LTE Cell-IDs teilen
Cell-IDs in RNCID/CID bzw. eNodeB/Sector ID aufteilen
Legende
GPS
Satellit zur Positionsbestimmung verwendet
Satellit nicht zur Positionsbestimmung verwendet
Sensoren
Hohe Genauigkeit
Mittlere Genauigkeit
Niedrige Genauigkeit
Unzuverlässig
Netze
2G-Zelle
3G-Zelle
4G-Zelle
Offenes WLAN-Netz
WEP-Sicherheit
WPA-PSK (Personal)-Sicherheit
WPA-EAP (Enterprise)-Sicherheit
Ad-hoc-WLAN
Unbekanter WLAN-Typ
Karte
Standort von Quelle: %s
Veralteter Standort von beliebiger Quelle
WLAN-Netze sortieren
- Nach BSSID
- Nach Name
- Nach Kanal
- Nach Signalstärke
Offline-Karte benutzen
Karten auf dem Gerät rendern (Download einer Offline-Karte erforderlich)
Ordner für Offline-Karten
Pfad des Ordners für Offline-Karten:
Um nach dem Ordner für Offline-Karten zu suchen, benötigen Sie einen unterstützten Dateimanager.
OK
Abbrechen
Offline-Karte herunterladen
Kartenmaterial von Mapsforge laden
Karten-Cache löschen
Alle Kacheln werden neu heruntergeladen oder gerendert, hilfreich bei der Fehlersuche
Karten-Cache gelöscht
Offline-Karte herunterladen
Ordner ist leer
Ein Download für eine Karte mit gleichem Namen läuft bereits
Sie haben offensichtlich bereits eine aktuelle Version dieser Karte. Trotzdem herunterladen?
Ja
Nein
Alle Downloads sind abgeschlossen
Um Offline-Karten herunterzuladen, müssen Sie den Zugriff auf Dateien zulassen
Standort- und Funknetzinformationen werden erst angezeigt, wenn Sie den Zugriff auf den Standort Ihres Geräts zulassen
Um AGPS-Daten zu aktualisieren, müsen Sie den Zugriff auf den Standort Ihres Geräts zulassen
Um Offline-Karten zu benutzen, müssen Sie den Zugriff auf Dateien zulassen
SatStat benötigt Berechtigungen
Zum Zulassen oder Verweigern antippen
Cell-IDs in beiden Formaten anzeigen
Sowohl die geteilte als auch die ungeteilte Form werden angezeigt; die vorherige Option gibt an, welche Form zuerst erscheint
UTM-Koordinaten nicht definiert
Beim Abruf der Liste vom Server ist ein Fehler aufgetreten
Erneut versuchen
================================================
FILE: res/values-es/strings.xml
================================================
SatStat
Ajustes
Grabar
Terminar registración
Actualizar dados AGPS
Leyenda
Acerca de
Error escribiendo archivo
Almanecenamiento externo no disponible
Grabación iniciada
Grabación de dados
Toca para terminar la grabación
Actualización de dados AGPS solecitada – ten en cuenta que Android no informa del éxito de la operación
Imposible actualizar dados – inicia el navegador, inicia la sesión en red y vuelve a intentarlo
Ninguna red disponible, imposible actualizar dados AGPS
GPS
Sensores
Redes
Latitud
Longitud
Coordenadas
Decl.
Velocidad
Altitud
Última ubicación recibida
Rumbo
Orientación
Error
Satélites
TTFF (s)
–
N
NNE
NE
ENE
E
ESE
SE
SSE
S
SSO
SO
OSO
O
ONO
NO
NNO
Acceleración y gravedad
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotación
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientación
Azimut
Orientación
Cabeceo
Alabeo
Campo magnetico
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorología
°C
hPa
Humedad relativa (%)
Miscelánea
lux
Proximidad (cm)
GSM
MCC
MNC
Cell ID
LAC
ASU
CDMA
SID
NID
BSID
WiFi
BSSID (MAC)
C.
dBm
Acerca de
Location, Sensor and Radio Network Status
Versión
<p>
Si no funciona algo como debería,
o si tienes una idea como mejorar aún esa aplicación,
por favor señalalo a:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p><p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p><p>
You should have received a copy of the GNU General Public License
along with this program. If not, see
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
El código fuente de esa aplicación se encuentra a:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
kn
m
Notificación de ubicación GPS
Notificarme cuando una aplicación obtenga mi ubicación desde GPS
Notificación de busqueda GPS
Notificarme cuando una aplicación busque mi ubicación
Ajustes
Buscando ubicación
Toca para ver el estado
Notificaciones
Actualización AGPS
En conexión WiFi
Actualizar dados AGPS al establecer una conexión a una red WiFi
Frecuencia de actualización
- Cada vez
- Una vez cada día
- Una vez cada 2 días
- Una vez cada 3 días
- Una vez cada 4 días
- Una vez cada 5 días
- Una vez cada 7 días
- Una vez cada semana
Última actualización
Redes
Actualizar dados AGPS al establecer una conexión a una de las redes seleccionadas
Mapa
Fuentes de ubicación
Mostrar las fuentes de ubicación seleccionadas en el mapa
Representación de dados
Usar unidades métricas
Mostrar velocidad en nudos
Coordenadas
- Grados (notación decimal)
- Grados y minutos
- Grados, minutos y segundos
- MGRS
- UTM
Leyenda
GPS
Sensores
Exactitud alta
Exactitud media
Exactitud baja
Redes
Celda 2G
Celda 3G
Celda 4G
WiFi abierto
Seguridad WEP
Seguridad WPA-PSK (personal)
Seguridad WPA-EAP (enterprise)
WiFi ad hoc
Tipo WiFi desconocido
Mapa
Ubicación de fuente %s
Ordenar las redes WiFi
- Por BSSID
- Por nombre
- Por canal
- Por livel del señal
Usar mapa offline
Renderizar las mapas en el dispositivo (se necesita descargar una mapa offline)
Carpeta para mapas offline
Ruta a la carpeta para mapas offline:
Para poder seleccionar la carpeta, necesita instalar un administrador de archivos soportado.
Aceptar
Cancelar
Descargar mapa offline
Descargar mapas de Mapsforge
Borrar tiles en caché
Todos los tiles serán descargados o renderizados de nuevo, útil en caso de problemas
Los tiles en cache están borrados
Descargar mapa offline
La carpeta está vacía
Una descarga de una mapa del mismo nombre ya está en curso
Parece que ya tienes una copia actual de esta mapa. ¿Quieres descargarla sin embargo?
Si
No
Todas las descargas están terminadas
Para descargar mapas offline, tienes que permitir el acceso a archivos de tu dispositivo
Para ver ubicación o redes inalámbricas, tienes que permitir el acceso a la ubicación de tu dispositivo
Para actualizar dados AGPS, tienes que permitir el acceso a la ubicación de tu dispositivo
Para usar mapas offline, tienes que permitir el acceso a archivos de tu dispositivo
SatStat necesita permisos
Toca para permitir o rechazar
================================================
FILE: res/values-fr/strings.xml
================================================
SatStat
Configuration
Enregistrement
Stopper l\'enregistrement
Recharger les données AGPS
Légende
A propos
Erreur lors de l\'écriture du fichier
Stockage externe non disponible
Début d\'enregistrement
Enregistrement des données du capteur
Appuyer pour stopper l\'enregistrement
Demande de chargement des données AGPS
Impossible charger données – lancez navigateur, connectez-vous au réseau et réessayez
Aucun réseau disponible, impossible charger données AGPS
GPS
Capteurs
Radio
Carte
Latitude
Longitude
Coordonnées
Decl.
Vitesse
Altitude
Obtention du dernier Fix
Bearing
Orientation
Erreur
Satellites
TTFF (s)
–
N
NNE
NE
ENE
E
ESE
SE
SSE
S
SSO
SO
OSO
O
ONO
NO
NNO
Accélération et gravité
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotation
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientation
Azimuth
Orientation
Pitch
Roll
Champ magnétique
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Météorologie
°C
hPa
Humidité relative (%)
Divers
lux
Proximité (cm)
GSM
MCC
MNC
Cell ID
LAC
PSC
ASU
CDMA
SID
NID
BSID
LTE
TAC
PCI
WiFi
BSSID (MAC)
Ch
dBm
A propos
Location, Sensor and Radio Network Status
Version
<p>
Si quelque chose ne fonctionne pas comme il le devrait,
ou si vous avez une idée pour rendre l\'application meilleure, vous pouvez
soumettre vos idées à l\'adresse:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
Ce programme est un logiciel libre; vous pouvez le redistribuer ou le
modifier suivant les termes de la GNU General Public License telle que publiée
par la Free Software Foundation; soit la version 3 de la licence,
soit (à votre gré) toute version ultérieure.
</p><p>
Ce programme est distribué dans l\'espoir qu\'il sera utile, mais SANS
AUCUNE GARANTIE; pas même la garantie implicite de COMMERCIABILISABILITÉ
ni d\'ADÉQUATION à UN OBJECTIF PARTICULIER. Consultez la GNU General Public
License pour plus de détails.
</p><p>
Vous devez avoir reçu une copie de la GNU General Public License en même
temps que ce programme; si ce n\'est pas le cas, consultez
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Le code source de cette application est disponible à l\'adresse:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
mph
kn
m
Notification de localisation GPS
Me notifier lorsqu\'une application m\'a localisé via GPS
Notification de recherche GPS
Me notifier lorsqu\'une application cherche ma position via GPS
Configuration
Recherche de position
Appuyez pour voir l\'état
Notifications
Mise à jour AGPS
A la connexion WiFi
Recharger données AGPS lorsque mon appareil se connecte à un réseau WiFi
Fréquence
- Chaque fois
- Une fois par jour
- Une fois chaque 2 jours
- Une fois chaque 3 jours
- Une fois chaque 4 jours
- Une fois chaque 5 jours
- Une fois chaque 6 jours
- Une fois par semaine
Dernière mise à jour
Réseaux
Recharger données AGPS lorsque mon appareil se connecte à un des réseaux selectionnés
Carte
Sources de localisation
Montrer les sources de localisation selectionnées sur la carte
Affichage des données
Utiliser des unités métriques
Si ce n\'est pas coché, les unités impériales seront utilisées sauf dans la vue des capteurs
Montrer vitesse en nœuds
Si ce n\'est pas coché, les unités métriques ou impériales (selon ce qui est sélectionné plus haut) seront utilisées
Coordonnées
- Degrés
- Degrés et minutes
- Degrés, minutes et secondes
- MGRS
- UTM
Afficher l\'heure du GPS en UTC
Afficher l\'heure du fix GPS en UTC au lieu de l\'heure locale
Séparer les identifiants de cellules UMTS/LTE
Séparer les identifiants de cellules en RNCID/CID ou eNodeB/Sector ID
Légende
GPS
Satellite utilisé pour le fix
Satellite non utilisé pour le fix
Capteurs
Justesse haute
Justesse moyenne
Justesse baisse
Pas fiable
Radio
Cellule 2G
Cellule 3G
Cellule 4G
WiFi ouvert
Sécurité WEP
Sécurité WPA-PSK (personal)
Sécurité WPA-EAP (enterprise)
WiFi ad hoc
Type de réseau WiFi inconnu
Carte
Localisation fournie par %s
Dernière localisation connue (périmée)
GPS
SBAS
55–64
GLONASS
97–192
QZSS
Beidou
Trier les reseaux WiFi
- Par BSSID
- Par nom
- Par canal
- Par niveau du signal
Utiliser la carte hors connexion
Rendu des cartes sur l\'appareil (nécessite le téléchargement d\'une carte hors connexion)
Dossier des cartes hors connexion
Chemin du dossier des cartes hors connexion:
Pour parcourrir le dossier des cartes hors connexion, vous devez installer un gestionnaire de fichiers compatible.
Gestionnaire de fichier CM
Gestionnaire de fichiers OI
OK
Annuler
Télécharger la carte hors connexion
Télécharger des cartes de Mapsforge
Effacer le cache des tuiles de carte
Forcer le téléchargement ou le rendu des tuiles, utile en cas de problème
Cache des tuiles de carte effacé
Télécharger la carte hors connexion
Le dossier est vide
Le téléchargement d\'une carte du même nom est déjà en cours.
Il semble que vous ayez déjà une copie à jour de cette carte. Voulez-vous vraiment la télécharger ?
Oui
Non
Tous les téléchargements sont terminés
Pour télécharger les cartes hors connexion, vous devez donner la permission d\'accéder aux fichiers
Aucune position ou information radio ne peut être affichée sans la permission d\'accéder à la position de l\'appareil
Pour rafraîchir les données AGPS, vous devez donner la permission d\'accéder à la position de l\'appareil
Pour utiliser les cartes hors connexion, vous devez donner la permission d\'accéder aux fichiers
SatStat requiert des permissions
Toucher pour accepter ou refuser
Afficher les deux formats de cellule
Afficher les deux formes fractionnées et non fractionnées, avec celui sélectionné ci-dessus apparaissant au-dessus
================================================
FILE: res/values-hu/strings.xml
================================================
SatStat
Beállítások
Rögzítés
Rögzítés megállítása
AGPS adatok újratöltése
Jelmagyarázat
Névjegy
Hiba a fájlba írás közben
A külső tároló nem elérhető
Rögzítés elindítva
Érzékelő adatok rögzítése
Érintse meg a rögzítés leállításához
AGPS adat újratöltése kérvényezve - az Android nem ad tájékoztatást, hogy sikerült-e a frissítés
Nem sikerült frissíteni az adatot – indítsa el a böngészőt, jelentkezzen be a hálózatra, és próbálja meg újra
Nincs elérhető hálózat, nem lehet újratölteni az AGPS adatokat
GPS
Érzékelők
Vezeték nélküli
Térkép
Szélesség
Hosszúság
Koordináták
Dekl.
Sebesség
Magasság
Legutóbbi fix pozíció
Hal. irány
Néz. irány
Hiba
Műholdak
TTFF (mp)
–
É
ÉÉK
ÉK
KÉK
K
KDK
DK
DDK
D
DDNy
DNy
NyDNy
Ny
NyÉNy
ÉNy
ÉÉNy
Gyorsulás és gravitáció
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Elfordulás
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Irány
Azimut
Égtáj
Billenés
Dőlés
Mágneses mező
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorológia
°C
hPa
Relatív páratartalom (%)
Egyebek
lux
Közelség (cm)
GSM
MCC
MNC
Cella ID
LAC
PSC
ASU
CDMA
SID
NID
BSID
LTE
TAC
PCI
WiFi
BSSID (MAC)
Ch
dBm
Névjegy
Location, Sensor and Radio Network Status
Verzió
<p>
If something doesn\'t work the way it should, or if you have an idea to make this
app even greater, please report it at:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p><p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p><p>
You should have received a copy of the GNU General Public License
along with this program. If not, see
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
The source code for this application is at:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
mph
kn
m
ft
GPS fix pozíció értesítés
Értesítsen, ha egy program megkapja a GPS pozíciómat
GPS keresési értesítés
Értesítsen, ha egy program keresi a GPS pozíciómat
Beállítások
Pozíció keresése
Érintse meg az állapot megtekintéséhez
Értesítések
AGPS adat frissítése
WiFi kapcsolatkor
AGPS adat frissítése, ha az eszközöm WiFi hálózaton van
Frissítési gyakoriság
- Minden alkalommal
- Naponta egyszer
- Kétnaponta
- Háromnaponta
- Négynaponta
- Ötnaponta
- Hatnaponta
- Hetente egyszer
Utolsó frissítés
Hálózatok
AGPS adat frissítése, ha az eszközöm valamelyik kiválasztott hálózathoz csatlakozik
Térkép
Pozíció források
Kiválasztott pozícióforrások megjelenítése a térképen
Adatmegjelenítés
Metrikus mértékegységek használata
Kikapcsolva angolszász mértékegységek fognak megjelenni (kivéve az érzékelők lapján)
Sebesség megjelenítése csomóban
Ha nincs bejelölve, metrikus vagy angolszász mértékegységek (a fentebb kiválasztottak szerint) fognak megjelenni
Koordináták
- Tizedes fok
- Fok, tizedes perc
- Fok, perc, másodperc
- MGRS
- UTM
GPS idő megjelenítése UTC-ben
A GPS fix pozíció megjelenítése UTC szerint helyi idő helyett.
UMTS/LTE cella ID-k különválasztása
Cella ID-k különválasztása RNCID/CID vagy eNodeB/sector ID szerint
Jelmagyarázat
GPS
Fix pozícióhoz felhasznált műholdak
Fix pozícióhoz fel nem használt műholdak
Érzékelők
Nagy pontosság
Közepes pontosság
Alacsony pontosság
Megbízhatatlan
Vezeték nélküli
2G cella
3G cella
4G cella
Nyílt WiFi
WEP titkosítás
WPA-PSK titkosítás
WPA-EAP titkosítás
Ad-hoc WiFi
Ismeretlen WiFi típus
Térkép
Pozíció %s forrásból
Pontatlan pozíció bármely forrásból
GPS
SBAS
55–64
GLONASS
97–192
QZSS
Beidou
236–300
Galileo
WiFi hálózatok rendezése
- BSSID szerint
- Név szerint
- Csatorna szerint
- Jel erősség szerint
Offline térkép használata
Térkép rajzolása az eszközön (le kell tölteni hozzá egy térképet)
Térkép könyvtára
Elérési út a térkép könyvtárához:
A térkép könyvtárának kitallózásához telepíteni kell egy támogatott fájlkezelőt.
CM File Manager
OI File Manager
OK
Mégse
Offline térkép letöltése
Térképek beszerzése a Mapsforge-tól
Térképcsempék gyorsítótárának törlése
Az összes térképcsempe újratöltésének vagy újrarajzolásának kényszerítése. Hibakereséshez hasznos lehet
A térképcsempék gyorsítótára kiürítve
Offline térkép letöltése
A könyvtár üres
Egy azonos nevű térkép letöltése már folyamatban van
Úgy tűnik, már megvan a legfrissebb változatod ebből a térképből. Biztos le akarod tölteni?
Igen
Nem
Minden letöltés befejeződött
Offline térkép letöltéséhez engedélyezned kell a hozzáférést a Tárolóhoz
A pozíció és a hálózati információk megjelenítéséhez engedélyezned kell a hozzáférést az eszköz helyadataihoz
Az AGPS adatok frissítéséhez engedélyezned kell a hozzáférést az eszköz helyadataihoz
Offline térkép letöltéséhez engedélyezned kell a hozzáférést a Tárolóhoz
A SatStat megigényli a szükséges jogokat
Érintsd meg az engedélyezéshez vagy tiltáshoz
Mindkét cella ID formátum megjelenítése
Megjelenítés különválasztott és egyben lévő formában is. A fentebb kiválasztott formátum jelenik meg felül.
A pozíció kívül esik az UTM tartományán
Hiba történt a térképlista letöltése közben
Újra
================================================
FILE: res/values-it/strings.xml
================================================
SatStat
Configurazione
Registra
Termina registrazione
Ricarico dati AGPS
Legenda
A proposito
Errore scrivendo file
Memoria esterna non disponibile
Registrazione iniziata
Registrazione dati
Tocca per terminare la registrazione
Richiesto ricarico dati AGPS – tieni conto che Android non ne rileva l\'esito
Impossibile ricaricare dati – avvia browser, accedi alla rete e riprova
Nessuna rete disponibile, impossibile ricaricare dati AGPS
GPS
Sensori
Radio
Latitudine
Longitudine
Coordinati
Decl.
Velocità
Altitudine
Ultima posizione ricevuta
Rotta
Orientation
Errore
Satelliti
TTFF (s)
–
N
NNE
NE
ENE
E
ESE
SE
SSE
S
SSO
SO
OSO
O
ONO
NO
NNO
Accelerazione e gravità
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotazione
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientamento
Azimuth
Orientation
Becch.
Rollio
Campo magnetico
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorologia
°C
hPa
Umidità relativa (%)
Diversi
lux
Prossimità (cm)
GSM
MCC
MNC
Cell ID
LAC
ASU
CDMA
SID
NID
BSID
WiFi
BSSID (MAC)
C
dBm
A proposito
Location, Sensor and Radio Network Status
Versione
<p>
Se qualcosa non funziona come dovrebbe,
ovvero se hai un\'idea come migliorare ancora quest\'applicazione,
segnalalo a:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p><p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p><p>
You should have received a copy of the GNU General Public License
along with this program. If not, see
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
Il codice sorgente di quest\'applicazione si trova a:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
kn
m
Notifica posizione GPS
Avvisa quando un\'applicazione ottiene la mia posizione dal GPS
Notifica ricerca GPS
Avvisa quando un\'applicazione cerca di individuare la mia posizione tramite GPS
Configurazione
Ricerca posizione
Tocca per vedere lo stato
Notifiche
Ricarico dati AGPS
Al collegamento WiFi
Aggiorna dati AGPS quando il mio dispositivo si collega a una rete WiFi
Frequenza aggiornamenti
- Ogni volta
- Una volta al giorno
- Una volta ogni 2 giorni
- Una volta ogni 3 giorni
- Una volta ogni 4 giorni
- Una volta ogni 5 giorni
- Una volta ogni 6 giorni
- Una volta ogni settimana
Ultimo aggiornamento
Reti
Aggiorna dati AGPS quando il mio dispositivo si collega a una delle reti selezionati
Mappa
Fonti di localizzazione
Mostrare le fonti di localizzazione selezionati sulla mappa
Rappresentazione dati
Usare unità metriche
Se non selezionato, verranno utilizzate delle unità imperiali (eccetto per i sensori)
Mostrare velocità en nodi
Se non selezionato, verranno delle unità metriche o imperiali (secondo l\'impostazione qui sopra) will be used
Coordinati
- Gradi decimali
- Gradi, minuti
- Gradi, minuti, secondi
- MGRS
- UTM
Mostrare ora GPS in UTC
Usare GPS anziché il fuso orario locale per mostrare il tempo GPS
Suddividere cell ID UMTS/LTE
Suddividere gli ID di cellule UMTS o LTE in RNCID/CID o eNodeB/sector ID
Legenda
GPS
Satellite utilizzato per la localizzazione
Satellite non utilizzato per la localizzazione
Sensori
Accuratezza alta
Accuratezza media
Accuratezza bassa
Inaffidabile
Radio
Cellula 2G
Cellula 3G
Cellula 4G
WiFi aperto
Sicurezza WEP
Sicurezza WPA-PSK (personal)
Sicurezza WPA-EAP (enterprise)
WiFi ad-hoc
Tipo WiFi sconosciuto
Mappa
Posizione di fonte: %s
Posizione superata di qualsiasi fonte
Ordina reti WiFi
- Per BSSID
- Per nome
- Per canale
- Per potenza segnale
Utilizza mappa offline
Renderizza le mappe sul dispositivo (bisogna scaricare una mappa offline)
Cartella per mappe offline
Percorso della cartella per mappe offline:
Per poter selezionare la cartella, bisogna installare un file manager supportato.
OK
Annulla
Scarica mappa offline
Scarica mappe di Mapsforge
Svuotare tile cache
Tutte le tile saranno scaricate o renderizzate di nuovo, utile in caso di problemi
Tile cache svuotato
Scarica mappa offline
La cartella è vuota
Un download per una mappa dello stesso nome è già in corso
Sembra che già abbia una copia attuale di questa mappa. Vuoi scaricarla lo stesso?
Sì
No
Tutti i download hanno finito
Per scaricare delle mappe offline, bisogna consentire l\'accesso alle file sul dispositivo
Per vedere posizione o reti, bisogna consentire l\'accesso alla posizione di questo dispositivo
Per ricaricare dati AGPS, bisogna consentire l\'accesso alla posizione di questo dispositivo
Per utilizzare delle mappe offline, bisogna consentire l\'accesso alle file sul dispositivo
SatStat sta richiedendo permessi
Tocca per consentire o negare
Mostrare cell ID in entrambi formati
Mostrare sia la forma suddivisa che quella combinata (quella selezionata sopra verrà mostrata sopra l\'altra)
Errore nello scaricamento della lista di mappe dal server
Riprova
================================================
FILE: res/values-lt/strings.xml
================================================
SatStat
Nustatymai
Įrašyti
Pabaigti įrašymą
Atnaujinti AGPS duomenis
Legenda
Apie
Error writing to file
External storage not available
Started recording
Recording sensor data
Touch to stop recording
AGPS duomenų atnaujinimo užklausa išsiųsta – dėmesio, kad apie atnaujinimo sekmę nepranešiama
Negalima atnaujinti duomenų – paleiskite naršyklę, prisijunkite prie tinklo ir bandykite dar kartą
Galimo tinklo nėra, negalima atnaujinti AGPS duomenų
GPS
Jutikliai
Tinklai
Platuma
Ilguma
Koordinatės
Dekl.
Greitis
Altitudė
Paskutinis padėties nustatymas
Kryptis
Orientation
Tiksl.
Palydovai
TTFF (s)
–
Š
ŠŠR
ŠR
RŠR
R
RPR
PR
PPR
P
PPV
PV
VPV
V
VŠV
ŠV
ŠŠV
Pagreitis ir gravitacija
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Sukimas
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientacija
Azimutas
Orientation
Išilginė
Šoninė
Magnetinis laukas
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorologija
°C
hPa
Santyk. drėgnumas (%)
Įvairūs
lux
Artumas (cm)
GSM
MCC
MNC
Cell ID
LAC
ASU
CDMA
SID
NID
BSID
WiFi
BSSID (MAC)
K.
dBm
Apie
Location, Sensor and Radio Network Status
Versija:
<p>
Jeigu kažkas neveikia kaip turėtų, ar jeigu turite
mintį kaip dar tobulinti šią programą, prašome pranešti čia:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow and contributors
<br/>
Portions © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p><p>
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p><p>
You should have received a copy of the GNU General Public License
along with this program. If not, see
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Map data and information provided by MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
and contributors,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
Šios programos išeitinis kodas yra čia:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
m
GPS vietovės įspėjimas
Pranešti, kai programa nustatys mano vietovę naudodama GPS
GPS paieškos įspėjimas
Pranešti, kai programa ieškos mano vietovės naudodama GPS
Nustatymai
Ieškoma vietovė
Palieskite, norėdami žiūrėti padėtį
Pranešimai
AGPS duomenų atnaujinimas
Prisijungus prie WiFi tinklo
Atnaujinti AGPS duomenis, kai mano įranga prisijungs prie WiFi tinklo
Atnaujinimų dažnis
- Kiekvieną kartą
- Vieną kartą kasdien
- Vieną kartą kas 2 dienas
- Vieną kartą kas 3 dienas
- Vieną kartą kas 4 dienas
- Vieną kartą kas 5 dienas
- Vieną kartą kas 6 dienas
- Vieną kartą kas savaitę
Paskutinkart atnaujinta
Tinklai
Atnaujinti AGPS duomenis, kai mano įranga prisijungs prie vieno iš išrinktųjų tinklų
Žemėlapis
Vietovės šaltiniai
Nurodyti išrinktuosius vietovės šaltinius žemėlapyje
Duomenų nurodymas
Naudoti metrinius vienetus
Jeigu neišrinkta, bus naudojami JAV matavimo vinetai (išskyrus jutikliams)
Nurodyti greitį mazgais
Jeigu neišrinkta, bus naudojami metrini arba JAV vienetai (pagal bendrą vienetų nustatymą) will be used
Koordinatės
- Laipsniais
- Laipsniais ir minutėmis
- Laipsniais, minutėmis ir sekundėmis
- MGRS
- UTM
Nurodyti GPS laiką UTC
Naudoti UTC vietienio laiko juosto vietoj GPS laiko nurodymui
Sudalyti UMTS/LTE ląstelių ID kodus
Sudalyti ID kodus į RNCID/CID arba eNodeB/sector ID
Legenda
GPS
Palydovas vartojamas vietovei nustatyti
Palydovas nevartojamas vietovei nustatyti
Jutikliai
Aukštas tikslumas
Vidutinis tikslumas
Žemas tikslumas
Nepatikimas
Tinklai
2G ląstelė
3G ląstelė
4G ląstelė
Atviras WiFi tinklas
WEP saugumas
WPA-PSK (asmeninis) saugumas
WPA-EAP (verslo) saugumas
Ad-hoc WiFi
Nepažįstama WiFi rūšis
Žemėlapis
%s šaltinio vietovė
Pasenusi bet kurio šaltinio vietovė
Ryšioti WiFi tinklus
- Pagal BSSID
- Pagal pavadimimą
- Pagal kanalą
- Pagal signalo galią
Naudoti offline žemėlapį
Atvaizduoti žemėlapius įrenginyje (reikės atsisiųsti offline žemėlapį)
Offline žemėlapių aplankas
Offline žemėlapių aplankas:
Kad galėtumete peržvelgti aplankus, reikia įdiegti palaikomą failų tvarkyklę.
Gerai
Atšaukti
Atsisiųsti offline žemėlapį
Atsisiųsti Mapsforge žemėlapių
Išvalyti talpykloje saugomas kaladėles
Visos žemėlapio kaladėlės bus parsiunčiamos arba atvaizduojamos iš naujo, naudinga bėdašaudystei
Talpykloje saugomos kaladėlės išvalytos
Atsisiųsti offline žemėlapį
Aplankas yra tuščias
Jau vyksta to pačio pavadinimo žemėlapio atsisiuntimas
Atrodo, kad jau turite šio žemėlapio naujausią versiją. Ar jį norite vis tiek atsisiųsti?
Taip
Ne
Visi atsisiuntimai yra pabaigti
Kad galėtumete atsisiųsti offline žemėlapius, reikia leidimo failus įrenginyje pasiekti
Kad galėtumete matyti vietovės arba bevielių tinklų informacijas, reikia leidimo įrenginio vietovės informaciją pasiekti
Kad galėtumete atnaujinti AGPS duomenis, reikia leidimo įrenginio vietovės informaciją pasiekti
Kad galėtumete naudoti offline žemėlapius, reikia leidimo failus įrenginyje pasiekti
SatStat prašo leidimus
Palieskite, norėdami leisti arba atmesti
Nurodyti abi ląstelių ID formas
Nurodyti ir sudalytą ir nesudalytą formą (anksčiau išrinkta forma bus nurodama viršuje)
Klaida parsiunčiant žemelapių sąraša
Pabandyti dar kartą
================================================
FILE: res/values-pt-rBR/strings.xml
================================================
SatStat
Configurações
Gravar
Parar gravação
Recarregar dados AGPS
Legenda
Sobre
Erro ao escrever para arquivo
Armazenamento externo não disponível
Gravação iniciada
Gravando dados do sensor
Toque para parar a gravação
Dados AGPS recarregados solicitados – note que o Android não reporta sucesso ou falha
Não foi possível atualizar os dados – abra o navegador, autentique-se na rede e tente novamente
Sem rede disponível, não foi possível recarregar os dados AGPS
GPS
Sensores
Rádio
Mapa
Latitude
Longitude
Coordenadas
Decl.
Velocidade
Altitude
Último Fix Obtido
Rolamento
Orientação
Erro
Satélites
TTFF (s)
–
N
NNE
NE
ENE
E
ESE
SE
SSE
S
SSW
SW
WSW
W
WNW
NW
NNW
Aceleração e Gravidade
Σg (m/s²)
gx (m/s²)
gy (m/s²)
gz (m/s²)
Rotação
Σω (rad/s)
ωx (rad/s)
ωy (rad/s)
ωz (rad/s)
Orientação
Azimute
Orientação
Passo
Rolo
Campo magnético
ΣB (µT)
Bx (µT)
By (µT)
Bz (µT)
Meteorologia
°C
hPa
Umidade relativa (%)
Diversas
lux
Proximidade (cm)
GSM
MCC
MNC
ID da célula
LAC
PSC
ASU
CDMA
SID
NID
BSID
LTE
TAC
PCI
WiFi
BSSID (MAC)
Ch
dBm
Sobre
Localização, Sensor e Estado da Rede de Rádio
Versão
<p>
Se alguma coisa não funcionar da forma que deveria, ou se você tem uma ideia para fazer este
aplicativo ainda maior, por favor informe-a em:
<br/>
<a href="https://github.com/mvglasow/satstat/issues>https://github.com/mvglasow/satstat/issues</a>
</p><p>
Copyright © 2013 – 2018 Michael von Glasow e contribuidores
<br/>
Porções © Jonathan Stott, k9mail, mapsforge.org, Polidea
</p><p>
Este programa é um software livre; você pode redistribuí-lo e/ou
modificá-lo dentro dos termos da Licença Pública Geral GNU como
publicada pela Fundação do Software Livre (FSF); na versão 3 da
Licença, ou (na sua opinião) qualquer versão.
</p><p>
Este programa é distribuído na esperança de que possa ser útil,
mas SEM NENHUMA GARANTIA; sem uma garantia implícita de ADEQUAÇÃO
a qualquer MERCADO ou APLICAÇÃO EM PARTICULAR. Veja a
Licença Pública Geral GNU para maiores detalhes.
</p><p>
Você deve ter recebido uma cópia da Licença Pública Geral GNU junto
com este programa. Se não, veja
<a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
</p><p>
Dados e informações de mapa fornecidos por MapQuest,
<a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>
e contribuidores,
<a href="http://wiki.openstreetmap.org/wiki/Legal_FAQ#3a._I_would_like_to_use_OpenStreetMap_maps._How_should_I_credit_you.3F">ODbL</a>.
</p><p>
O código-fonte para este aplicativo está em:
<br/>
<a href="https://github.com/mvglasow/satstat>https://github.com/mvglasow/satstat</a>
°
km/h
mph
m
ft
Arrumar notificação do GPS
Notifique-me quando um aplicativo obter minha localização do GPS
Notificação de busca do GPS
Notifique-me quando um aplicativo estiver procurando pela minha localização
Configurações
Busca por localização
Toque para ver o estado
Notificações
Atualização de dados AGPS
Ao conectar no WiFi
Atualizar dados AGPS quando meu dispositivo se conectar a uma rede WiFi
Frequência de atualização
- A cada hora
- Uma vez por dia
- Uma vez a cada 2 dias
- Uma vez a cada 3 dias
- Uma vez a cada 4 dias
- Uma vez a cada 5 dias
- Uma vez a cada 6 dias
- Uma vez por semana
Última atualização
%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS
Redes
Atualizar dados AGPS quando meu dispositivo se conectar a uma das redes selecionadas
Mapa
Fontes de Localização
Exibir fontes de localização selecionadas no mapa
Exibição de dados
Usar unidades métricas
Se não checado, unidades imperiais serão utilizadas (exceto em visualização de sensor)
Coordenadas
- Decimal graus
- Graus, minutos
- Graus, minutos, segundos
- MGRS
- UTM
Exibir hora do GPS em UTC
Exibir hora corrigida do GPS em UTC em vez do horário local
Dividir identificação de células UMTS/LTE
Dividir identificação de células em RNCID/CID ou eNodeB/sector ID
Legenda
GPS
Satélite usado na correção
Satélite não usado na correção
Sensores
Precisão alta
Precisão média
Precisão baixa
Não confiável
Rádio
Célula 2G
Célula 3G
Célula 4G
WiFi aberto
Segurança WEP
Segurança WPA-PSK (pessoal)
Segurança WPA-EAP (empresarial)
WiFi Ad-hoc
Tipo de WiFi desconhecido
Mapa
Localização do provedor %s
Localização velha de qualquer provedor
GPS
SBAS
55–64
GLONASS
97–192
QZSS
Beidou
Ordenar redes WiFi
- Por BSSID
- Por nome
- Por canal
- Por intensidade do sinal
Usar mapa offline
Renderizar mapas no dispositivo (requer baixar um mapa offline)
Pasta de mapas offline
Caminho para a pasta de mapas offline:
Para navegar para a pasta de mapas offline, você precisa instalar um gerenciador de arquivos suportado.
CM File Manager
OI File Manager
OK
Cancelar
Baixar mapa offline
Limpar cache de ladrilos de mapa
Forçar todos os ladrilhos a serem buscados ou renderizados novamente, útil para solução de problemas
Cache de ladrilhos de mapas limpo
Baixar mapa offline
A pasta está vazia
Um download para um mapa de mesmo nome já está em progresso
Parece que você já possui uma cópia atualizada deste mapa. Você quer baixá-lo mesmo assim?
Sim
Não
Todos os downloads foram completados
Para baixar mapas offline, você deve permitir o acesso a seus arquivos
Nenhuma informação de localização ou rádio será mostrada a menos que você autorize o acesso à localização do dispositivo
Para recarregar os dados AGPS, você deve permitir o acesso à localização do dispositivo
Para usar mapas offline, você deve permitir o acesso a seus arquivos
SatStat está pedindo permissões
Toque para permitir ou negar
Mostrar ambos formatos de ID de célula
Mostrar tanto a forma dividida quanto a não dividida, com a selecionada aparecendo no topo
================================================
FILE: res/values-sw600dp/dimens.xml
================================================
================================================
FILE: res/values-sw720dp-land/dimens.xml
================================================
128dp
================================================
FILE: res/values-v21/styles.xml
================================================
================================================
FILE: res/xml/preferences.xml
================================================
================================================
FILE: setbuild.sh
================================================
#!/bin/bash
git update-index --assume-unchanged res/raw/build.txt
echo git-`git log -1 --pretty=format:%h``git diff-index --quiet HEAD || echo -dirty` > res/raw/build.txt
================================================
FILE: src/com/hzi/UTM.java
================================================
package com.hzi;
import android.content.Context;
import android.content.res.Resources;
import com.vonglasow.michael.satstat.R;
import java.lang.Math;
/**
* Created by HZI on 3/3/15.
*
* based on http://robotics.ai.uiuc.edu/~hyoon24/LatLongUTMconversion.py
*/
public class UTM {
// Test function
public static double hzi01(double x) {
return (x / 10);
}
public static String lat_lon_to_utm(double Lat, double Long, Context c) {
double deg2rad = Math.PI / 180.0;
double rad2deg = 180.0 / Math.PI;
// Parameters for WGS-84
double a = 6378137.0;
double eccSquared = 0.00669438;
double k0 = 0.9996;
double LongTemp = (Long + 180) - (int) ((Long + 180) / 360) * 360 - 180;
int ZoneNumber = ((int) (LongTemp + 180) / 6) + 1;
double LatRad = Lat * deg2rad;
double LongRad = LongTemp * deg2rad;
if (Lat >= 56.0 && Lat < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0) {
ZoneNumber = 32;
}
// Special zones for Svalbard
if (Lat >= 72.0 && Lat < 84.0)
if (LongTemp >= 0.0 && LongTemp < 9.0)
ZoneNumber = 31;
else if (LongTemp >= 9.0 && LongTemp < 21.0) ZoneNumber = 33;
else if (LongTemp >= 21.0 && LongTemp < 33.0) ZoneNumber = 35;
else if (LongTemp >= 33.0 && LongTemp < 42.0) ZoneNumber = 37;
double LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3;
double LongOriginRad = LongOrigin * deg2rad;
double eccPrimeSquared = (eccSquared) / (1 - eccSquared);
double N = a / Math.sqrt(1 - eccSquared * Math.sin(LatRad) * Math.sin(LatRad));
double T = Math.tan(LatRad) * Math.tan(LatRad);
double C = eccPrimeSquared * Math.cos(LatRad) * Math.cos(LatRad);
double A = Math.cos(LatRad) * (LongRad - LongOriginRad);
double M = a * ((1 - eccSquared / 4
- 3 * eccSquared * eccSquared / 64
- 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad
- (3 * eccSquared / 8
+ 3 * eccSquared * eccSquared / 32
+ 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(2 * LatRad)
+ (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * Math.sin(4 * LatRad)
- (35 * eccSquared * eccSquared * eccSquared / 3072) * Math.sin(6 * LatRad));
double UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6
+ (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120)
+ 500000.0);
double UTMNorthing = (k0 * (M + N * Math.tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24
+ (61
- 58 * T
+ T * T
+ 600 * C
- 330 * eccPrimeSquared) * A * A * A * A * A * A / 720)));
if (Lat > 84 || Lat < -80) {
return (c.getString(R.string.utm_outside_latitude_range));
} else {
if (Lat < 0)
UTMNorthing = UTMNorthing + 10000000.0;
return (String.format("%d / %s / %,d / %,d", ZoneNumber, ((Lat > 0) ? "N" : "S"), Math.round(UTMEasting), Math.round(UTMNorthing)));
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/Const.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat;
import java.io.File;
import android.net.ConnectivityManager;
import android.os.Environment;
/**
* Constants used throughout the application.
*/
public class Const {
/**
* Milliseconds per day
*/
public static final long MILLIS_PER_DAY = 86400000;
/**
* Earth circumference in meters
*/
public static final double EARTH_CIRCUMFERENCE = 40000000; // meters
/* Intents */
public static final String GPS_ENABLED_CHANGE = "android.location.GPS_ENABLED_CHANGE";
public static final String GPS_FIX_CHANGE = "android.location.GPS_FIX_CHANGE";
public static final String AGPS_DATA_EXPIRED = "com.vonglasow.michael.satstat.AGPS_DATA_EXPIRED";
public static final String DOWNLOAD_RECEIVER_REGISTERED = "com.vonglasow.michael.satstat.DOWNLOAD_RECEIVER_REGISTERED";
/**
* Available location provider styles
*/
public static final String [] LOCATION_PROVIDER_STYLES = {
"location_provider_blue",
"location_provider_green",
"location_provider_orange",
"location_provider_purple",
"location_provider_red"
};
/**
* Index of the marker drawable in the location provider style
*/
public static final int STYLE_MARKER = 0;
/**
* Index of the stroke color in the location provider style
*/
public static final int STYLE_STROKE = 1;
/**
* Index of the fill color in the location provider style
*/
public static final int STYLE_FILL = 2;
/**
* Blue style: default for network location provider
*/
public static final String LOCATION_PROVIDER_BLUE = "location_provider_blue";
/**
* Red style: default for GPS location provider
*/
public static final String LOCATION_PROVIDER_RED = "location_provider_red";
/**
* Gray style for inactive location providers
*/
public static final String LOCATION_PROVIDER_GRAY = "location_provider_gray";
/* Preference keys */
public static final String KEY_PREF_NOTIFY_FIX = "pref_notify_fix";
public static final String KEY_PREF_NOTIFY_SEARCH = "pref_notify_search";
public static final String KEY_PREF_UPDATE_WIFI = "pref_update_wifi";
public static final String KEY_PREF_UPDATE_NETWORKS = "pref_update_networks";
public static final String KEY_PREF_UPDATE_NETWORKS_WIFI = Integer.toString(ConnectivityManager.TYPE_WIFI);
public static final String KEY_PREF_UPDATE_NETWORKS_MOBILE = Integer.toString(ConnectivityManager.TYPE_MOBILE);
public static final String KEY_PREF_UPDATE_FREQ = "pref_update_freq";
public static final String KEY_PREF_UPDATE_LAST = "pref_update_last";
public static final String KEY_PREF_LOC_PROV = "pref_loc_prov";
public static final String KEY_PREF_LOC_PROV_STYLE = "pref_loc_prov_style.";
public static final String KEY_PREF_MAP_LAT = "pref_map_lat";
public static final String KEY_PREF_MAP_LON = "pref_map_lon";
public static final String KEY_PREF_MAP_ZOOM = "pref_map_zoom";
public static final String KEY_PREF_UNIT_TYPE = "pref_unit_type";
public static final String KEY_PREF_KNOTS = "pref_knots";
public static final String KEY_PREF_MAP_OFFLINE = "pref_map_offline";
public static final String KEY_PREF_MAP_PATH = "pref_map_path";
public static final String KEY_PREF_MAP_CACHED_PATH = "pref_map_cached_path";
public static final String KEY_PREF_MAP_DOWNLOAD = "pref_map_download";
public static final String KEY_PREF_MAP_PURGE = "pref_map_purge";
public static final String KEY_PREF_COORD = "pref_coord";
public static final int KEY_PREF_COORD_DECIMAL = 0;
public static final int KEY_PREF_COORD_MIN = 1;
public static final int KEY_PREF_COORD_SEC = 2;
public static final int KEY_PREF_COORD_MGRS = 3;
public static final int KEY_PREF_COORD_UTM = 4;
public static final String KEY_PREF_UTC = "pref_utc";
public static final String KEY_PREF_CID = "pref_cid";
public static final String KEY_PREF_CID2 = "pref_cid2";
public static final String KEY_PREF_WIFI_SORT = "pref_wifi_sort";
/**
* Tile cache name for tiles downloaded from OSM
*/
public static final String TILE_CACHE_OSM = "OSM";
/**
* Tile servers for Mapquest
*/
public static final String[] TILE_SERVER_OSM = {
"a.tile.openstreetmap.org",
"b.tile.openstreetmap.org",
"c.tile.openstreetmap.org"
};
/**
* Tile URL for OSM (anything between server name and zoom level)
*/
public static final String TILE_URL_OSM = "";
/**
* Tile extension for OSM
*/
public static final String TILE_EXTENSION_OSM = "png";
/**
* Tile cache name for tiles rendered with internal render theme
*/
public static final String TILE_CACHE_INTERNAL_RENDER_THEME = "InternalRenderTheme";
/**
* MIME type for HTML
*/
public static final String CONTENT_TYPE_HTML = "text/html";
public static final String MAP_PATH_DEFAULT = new File(Environment.getExternalStorageDirectory(), "org.mapsforge/maps").getAbsolutePath();
/**
* Key for a previously saved state of an {@code Activity} instance
*/
public static final String KEY_SAVED_INSTANCE_STATE = "savedInstanceState";
/**
* Key for a {@link ResultReceiver}
*/
public static final String KEY_RESULT_RECEIVER = "resultReceiver";
/**
* Key for an array of permissions
*/
public static final String KEY_PERMISSIONS = "permissions";
/**
* Key for an array of permission grant results
*/
public static final String KEY_GRANT_RESULTS = "grantResults";
/**
* Key for a request code
*/
public static final String KEY_REQUEST_CODE = "requestCode";
/**
* Permission request when launching the map download dialog
*/
public static final int PERM_REQUEST_MAP_DOWNLOAD = 1;
/**
* Permission request when registering phone state listener
*/
public static final int PERM_REQUEST_PHONE_STATE_LISTENER = 2;
/**
* Permission request when refreshing AGPS data
*/
public static final int PERM_REQUEST_REFRESH_AGPS = 3;
/**
* Permission request when requesting location updates
*/
public static final int PERM_REQUEST_LOCATION_UPDATES = 4;
/**
* Permission request when requesting cell information
*/
public static final int PERM_REQUEST_CELL_INFO = 5;
/**
* Permission request on startup (all UI permissions)
*/
public static final int PERM_REQUEST_STARTUP = 6;
/**
* Permission request when using offline map
*/
public static final int PERM_REQUEST_OFFLINE_MAP = 7;
/**
* Permission request for location access, sent via notification
*/
public static final int PERM_REQUEST_LOCATION_NOTIFICATION = 8;
/**
* Permission request for location access made while changing settings
*/
public static final int PERM_REQUEST_LOCATION_PREF = 9;
/**
* Highest numerical value currently defined for any {@code PERM_REQUEST} constant
*/
public static final int PERM_REQUEST_MAX = 9;
}
================================================
FILE: src/com/vonglasow/michael/satstat/GpsEventReceiver.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.vonglasow.michael.satstat.utils.PermissionHelper;
import com.vonglasow.michael.satstat.utils.WifiCapabilities;
import android.Manifest;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningServiceInfo;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.widget.Toast;
public class GpsEventReceiver extends BroadcastReceiver {
public static final String TAG = GpsEventReceiver.class.getSimpleName();
/**
* A dummy intent called when a location update is received.
*
* Some devices will refresh AGPS data only when the GPS is accessed. Thus,
* in order to force an AGPS update, we request location updates from the
* GPS and immediately remove updates again. However, in order to request
* location updates we need to supply either a LocationListener or a
* PendingIntent to which the location updates will be delivered. This
* Intent is used to create that PendingIntent. When this Intent is
* received, it will be ignored.
*/
public static final String LOCATION_UPDATE_RECEIVED = "com.vonglasow.michael.satstat.LOCATION_UPDATE_RECEIVED";
private static Intent mAgpsIntent = new Intent(Const.AGPS_DATA_EXPIRED);
private static Intent mLocationIntent = new Intent(LOCATION_UPDATE_RECEIVED);
@Override
public void onReceive(Context context, Intent intent) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
// some logic to use the pre-1.7 setting KEY_PREF_UPDATE_WIFI as a
// fallback if KEY_PREF_UPDATE_NETWORKS is not set
Set fallbackUpdateNetworks = new HashSet();
if (sharedPref.getBoolean(Const.KEY_PREF_UPDATE_WIFI, false)) {
fallbackUpdateNetworks.add(Const.KEY_PREF_UPDATE_NETWORKS_WIFI);
}
Set updateNetworks = sharedPref.getStringSet(Const.KEY_PREF_UPDATE_NETWORKS, fallbackUpdateNetworks);
if (intent.getAction().equals(Const.GPS_ENABLED_CHANGE) || intent.getAction().equals(Const.GPS_ENABLED_CHANGE)) {
//FIXME: why are we checking for the same intent twice? Should on of them be GPS_FIX_CHANGE?
// an application has connected to GPS or disconnected from it, check if notification needs updating
boolean notifyFix = sharedPref.getBoolean(Const.KEY_PREF_NOTIFY_FIX, false);
boolean notifySearch = sharedPref.getBoolean(Const.KEY_PREF_NOTIFY_SEARCH, false);
if (notifyFix || notifySearch) {
boolean isRunning = false;
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (PasvLocListenerService.class.getName().equals(service.service.getClassName())) {
isRunning = true;
}
}
if (!isRunning) {
Intent startServiceIntent = new Intent(context, PasvLocListenerService.class);
startServiceIntent.setAction(intent.getAction());
startServiceIntent.putExtras(intent.getExtras());
context.startService(startServiceIntent);
}
}
} else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)
&& updateNetworks.contains(Const.KEY_PREF_UPDATE_NETWORKS_WIFI)) {
// change in WiFi connectivity, check if we are connected and need to refresh AGPS
//FIXME: KEY_PREF_UPDATE_WIFI as fallback only
NetworkInfo netinfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (netinfo == null) return;
if (!netinfo.isConnected()) return;
//Toast.makeText(context, "WiFi is connected", Toast.LENGTH_SHORT).show();
Log.i(this.getClass().getSimpleName(), "WiFi is connected");
refreshAgps(context, true, false);
} else if ((intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION )) ||
(intent.getAction().equals(Const.AGPS_DATA_EXPIRED))) {
// change in network connectivity or AGPS expiration timer fired
boolean isAgpsExpired = false;
if (intent.getAction().equals(Const.AGPS_DATA_EXPIRED)) {
Log.i(this.getClass().getSimpleName(), "AGPS data expired, checking available networks");
isAgpsExpired = true;
}
NetworkInfo netinfo = ((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (netinfo == null) return;
if (!netinfo.isConnected()) return;
String type;
if ((netinfo.getType() < ConnectivityManager.TYPE_MOBILE_MMS) || (netinfo.getType() > ConnectivityManager.TYPE_MOBILE_HIPRI)) {
type = Integer.toString(netinfo.getType());
} else {
// specific mobile data connections will be treated as TYPE_MOBILE
type = Const.KEY_PREF_UPDATE_NETWORKS_MOBILE;
}
if (!updateNetworks.contains(type)) return;
if (!isAgpsExpired)
Log.i(this.getClass().getSimpleName(), "Network of type " + netinfo.getTypeName() + " is connected");
// Enforce the update interval if we were called by a network event
// but not if we were called by a timer, because in that case the
// check has already been done. (I am somewhat paranoid and don't
// count on alarms not going off a few milliseconds too early.)
refreshAgps(context, !isAgpsExpired, false);
}
}
/**
* Refreshes AGPS data if necessary.
*
* This method requests a refresh of the AGPS data. It optionally does so
* only after checking when the AGPS data was last refreshed and
* determining if it is stale by adding the refresh interval specified in
* the user preferences and comparing the result against the current time.
* If the result is less than current time, AGPS data is considered stale
* and a refresh is requested.
*
* @param context A {@link Context} to be passed to {@link LocationManager}.
* @param enforceInterval If true, prevents updates when the interval has
* not yet expired. If false, updates are permitted at any time. This is to
* prevent race conditions if alarms fire off too early.
* @param wantFeedback Whether to display a toast informing the user about
* the success of the operation.
*/
public static void refreshAgps(Context context, boolean enforceInterval, boolean wantFeedback) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
long last = sharedPref.getLong(Const.KEY_PREF_UPDATE_LAST, 0);
long freqDays = Long.parseLong(sharedPref.getString(Const.KEY_PREF_UPDATE_FREQ, "0"));
long now = System.currentTimeMillis();
if (enforceInterval && (last + freqDays * Const.MILLIS_PER_DAY > now)) return;
//Log.d(GpsEventReceiver.class.getSimpleName(), String.format("refreshAgps, enforceInterval: %b, wantFeedback: %b", enforceInterval, wantFeedback));
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
new AgpsUpdateTask(wantFeedback).execute(context, mAgpsIntent, sharedPref, freqDays * Const.MILLIS_PER_DAY);
else {
Log.i(TAG, "Requesting permissions to update AGPS data");
PermissionHelper.requestPermissions((SatStatApplication) (context.getApplicationContext()),
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
Const.PERM_REQUEST_LOCATION_NOTIFICATION,
context.getString(R.string.notify_perm_title),
context.getString(R.string.notify_perm_body),
R.drawable.ic_security);
}
}
private static class AgpsUpdateTask extends AsyncTask {
Context mContext;
boolean mWantFeedback = false;
public AgpsUpdateTask(boolean wantFeedback) {
super();
mWantFeedback = wantFeedback;
}
/**
* @param args[0] A {@link Context} for connecting to the various system services
* @param args[1] The {@link Intent} to raise when the next update is due
* @param args[2] A {@link SharedPreferences} instance in which the timestamp of the update will be stored
* @param args[3] The update frequency, of type {@link Long}, in milliseconds
*/
@Override
protected Integer doInBackground(Object... args) {
mContext = (Context) args[0];
Intent agpsIntent = (Intent) args[1];
SharedPreferences sharedPref = (SharedPreferences) args[2];
long freqMillis = (Long) args[3];
int nc = WifiCapabilities.getNetworkConnectivity();
if (nc == WifiCapabilities.NETWORK_CAPTIVE_PORTAL) {
// portale cattivo che non ci permette di scaricare i dati AGPS
Log.i(GpsEventReceiver.class.getSimpleName(), "Captive portal detected, cannot update AGPS data");
return nc;
} else if (nc == WifiCapabilities.NETWORK_ERROR) {
Log.i(GpsEventReceiver.class.getSimpleName(), "No network available, cannot update AGPS data");
return nc;
}
AlarmManager alm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, agpsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alm.cancel(pi);
LocationManager locman = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
List allProviders = locman.getAllProviders();
PendingIntent tempIntent = PendingIntent.getBroadcast(mContext, 0, mLocationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
Log.i(GpsEventReceiver.class.getSimpleName(), "Requesting AGPS data update");
try {
if (allProviders.contains(LocationManager.GPS_PROVIDER))
locman.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, tempIntent);
locman.sendExtraCommand("gps", "force_xtra_injection", null);
locman.sendExtraCommand("gps", "force_time_injection", null);
locman.removeUpdates(tempIntent);
SharedPreferences.Editor spEditor = sharedPref.edit();
spEditor.putLong(Const.KEY_PREF_UPDATE_LAST, System.currentTimeMillis());
spEditor.commit();
} catch (SecurityException e) {
Log.w(GpsEventReceiver.class.getSimpleName(), "Permissions not granted, cannot update AGPS data");
}
if (freqMillis > 0) {
// if an update interval is set, prepare an alarm to trigger a new
// update when it elapses (if no interval is set, do nothing as we
// cannot determine a point in time for re-running the update)
long next = System.currentTimeMillis() + freqMillis;
alm.set(AlarmManager.RTC, next, pi);
Log.i(GpsEventReceiver.class.getSimpleName(), String.format("Next update due %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS (after %2$d ms)", next, freqMillis));
}
return nc;
}
@Override
protected void onPostExecute(Integer result) {
if ((mContext == null) || !mWantFeedback) return;
String message = "";
switch (result) {
case WifiCapabilities.NETWORK_AVAILABLE:
message = mContext.getString(R.string.status_agps);
break;
case WifiCapabilities.NETWORK_CAPTIVE_PORTAL:
message = mContext.getString(R.string.status_agps_captive);
break;
case WifiCapabilities.NETWORK_ERROR:
message = mContext.getString(R.string.status_agps_error);
break;
}
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show();
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/PasvLocListenerService.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat;
import com.vonglasow.michael.satstat.ui.MainActivity;
import com.vonglasow.michael.satstat.utils.PermissionHelper;
import uk.me.jstott.jcoord.LatLng;
import uk.me.jstott.jcoord.MGRSRef;
import android.Manifest;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback;
import android.support.v4.app.TaskStackBuilder;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import com.hzi.UTM;
public class PasvLocListenerService extends Service implements GpsStatus.Listener, LocationListener, OnSharedPreferenceChangeListener, OnRequestPermissionsResultCallback {
// The unique ID for the notification
private static final int ONGOING_NOTIFICATION = 1;
// GPS status values
private static final int GPS_INACTIVE = 0;
private static final int GPS_SEARCH = 1;
private static final int GPS_FIX = 2;
private int mStatus = GPS_INACTIVE;
private boolean prefUnitType = true;
private boolean prefKnots = false;
private int prefCoord = Const.KEY_PREF_COORD_DECIMAL;
private boolean mNotifyFix = false;
private boolean mNotifySearch = false;
private LocationManager mLocationManager;
private NotificationCompat.Builder mBuilder;
private SharedPreferences mSharedPreferences;
private BroadcastReceiver mGpsStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context c, Intent intent) {
if (intent == null) return;
if (intent.getAction().equals(Const.GPS_ENABLED_CHANGE) && !intent.getBooleanExtra("enabled", true)) {
// GPS_ENABLED_CHANGE, enabled=false: GPS disabled, dismiss notification
mStatus = GPS_INACTIVE;
stopForeground(true);
} else if (intent.getAction().equals(Const.GPS_FIX_CHANGE) && intent.getBooleanExtra("enabled", false)) {
// GPS_FIX_CHANGE, enabled=true: GPS got fix, will be taken care of in onLocationChanged
mStatus = GPS_FIX;
} else {
// GPS_ENABLED_CHANGE, enabled=true: GPS enabled
// GPS_FIX_CHANGE, enabled=false: GPS lost fix
mStatus = GPS_SEARCH;
showStatusNoLocation();
}
}
};
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
super.onCreate(); //do we need that here?
mLocationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
mNotifyFix = mSharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_FIX, mNotifyFix);
mNotifySearch = mSharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_SEARCH, mNotifySearch);
if (mNotifyFix || mNotifySearch)
requestPermissions();
registerReceiver(mGpsStatusReceiver, new IntentFilter(Const.GPS_ENABLED_CHANGE));
registerReceiver(mGpsStatusReceiver, new IntentFilter(Const.GPS_FIX_CHANGE));
}
@Override
public void onDestroy() {
stopForeground(true);
unregisterReceiver(mGpsStatusReceiver);
mLocationManager.removeUpdates(this);
mLocationManager.removeGpsStatusListener(this);
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
@Override
public void onGpsStatusChanged(int event) {
GpsStatus status = mLocationManager.getGpsStatus(null);
int satsUsed = 0;
Iterable sats = status.getSatellites();
for (GpsSatellite sat : sats) {
if (sat.usedInFix()) {
satsUsed++;
}
}
if (satsUsed == 0) {
if (mStatus != GPS_INACTIVE)
mStatus = GPS_SEARCH;
showStatusNoLocation();
}
}
@Override
public void onLocationChanged(Location location) {
if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) return;
if (mNotifyFix && (mStatus != GPS_INACTIVE)) {
mStatus = GPS_FIX;
GpsStatus status = mLocationManager.getGpsStatus(null);
int satsInView = 0;
int satsUsed = 0;
Iterable sats = status.getSatellites();
for (GpsSatellite sat : sats) {
satsInView++;
if (sat.usedInFix()) {
satsUsed++;
}
}
double lat = Math.abs(location.getLatitude());
double lon = Math.abs(location.getLongitude());
String ns = (location.getLatitude() > 0)?
getString(R.string.value_N):
(location.getLatitude() < 0)?
getString(R.string.value_S):"";
String ew = (location.getLongitude() > 0)?
getString(R.string.value_E):
(location.getLongitude() < 0)?
getString(R.string.value_W):"";
String title = "";
if (prefCoord == Const.KEY_PREF_COORD_DECIMAL) {
title = String.format("%.5f%s%s %.5f%s%s",
lat, getString(R.string.unit_degree), ns,
lon, getString(R.string.unit_degree), ew);
} else if (prefCoord == Const.KEY_PREF_COORD_MIN) {
double decY = lat;
double degY = (int) decY;
double minY = Math.abs(60.0 * (decY - degY));
double decX = lon;
double degX = (int) decX;
double minX = Math.abs(60.0 * (decX - degX));
title = String.format("%.0f%s %.3f' %s %.0f%s %.3f' %s",
degY, getString(R.string.unit_degree), minY + /*rounding*/ 0.0005, ns,
degX, getString(R.string.unit_degree), minX + /*rounding*/ 0.0005, ew);
} else if (prefCoord == Const.KEY_PREF_COORD_SEC) {
double decY = lat;
double degY = (int) decY;
double tmp = 60.0 * (decY - degY);
double minY = (int) Math.abs(tmp);
double secY = Math.abs(60.0 * (tmp - minY));
double decX = lon;
double degX = (int) decX;
tmp = 60.0 * (decX - degX);
double minX = (int) Math.abs(tmp);
double secX = Math.abs(60.0 * (tmp - minX));
title = String.format("%.0f%s %.0f' %.1f\" %s %.0f%s %.0f' %.1f\" %s",
degY, getString(R.string.unit_degree), minY, secY + /*rounding*/ 0.05, ns,
degX, getString(R.string.unit_degree), minX, secX + /*rounding*/ 0.05, ew);
} else if (prefCoord == Const.KEY_PREF_COORD_MGRS) {
title = new LatLng(location.getLatitude(), location.getLongitude()).toMGRSRef().toString(MGRSRef.PRECISION_1M);
} else if (prefCoord == Const.KEY_PREF_COORD_UTM) {
title = UTM.lat_lon_to_utm(location.getLatitude(), location.getLongitude(), this.getApplicationContext());
}
String text = "";
if (location.hasAltitude()) {
text = text + String.format("%.0f%s",
(location.getAltitude() * (prefUnitType ? 1 : 3.28084)),
getString(((prefUnitType) ? R.string.unit_meter : R.string.unit_feet)));
}
if (location.hasSpeed()) {
text = text + (text.equals("")?"":", ") + String.format("%.0f%s",
(location.getSpeed() * (prefKnots ? 1.943844 : prefUnitType ? 3.6 : 2.23694)),
getString(((prefKnots) ? R.string.unit_kn : (prefUnitType) ? R.string.unit_km_h : R.string.unit_mph)));
}
if (location.hasAccuracy()) {
text = text + (text.equals("")?"":", ") + String.format("\u03b5 = %.0f%s",
(location.getAccuracy() * (prefUnitType ? 1 : 3.28084)),
getString(((prefUnitType) ? R.string.unit_meter : R.string.unit_feet)));
}
text = text + (text.equals("")?"":", ") + String.format("%d/%d",
satsUsed,
satsInView);
text = text + (text.equals("")?"":",\n") + String.format("TTFF %d s",
status.getTimeToFirstFix() / 1000);
mBuilder.setSmallIcon(R.drawable.ic_stat_notify_location);
mBuilder.setContentTitle(title);
mBuilder.setContentText(text);
mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(text));
startForeground(ONGOING_NOTIFICATION, mBuilder.build());
} else {
stopForeground(true);
}
}
@Override
public void onProviderDisabled(String provider) {
// nop
}
@Override
public void onProviderEnabled(String provider) {
// nop
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
boolean isGranted = false;
for (int i = 0; i < grantResults.length; i++)
if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION) && (grantResults[i] == PackageManager.PERMISSION_GRANTED))
isGranted = true;
if (isGranted) {
requestLocationUpdates();
if (mNotifySearch && (mStatus != GPS_INACTIVE))
showStatusNoLocation();
}
else
Log.w("PasvLocListenerService", "ACCESS_FINE_LOCATION permission not granted. Location notifications will not be available.");
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key.equals(Const.KEY_PREF_NOTIFY_FIX) || key.equals(Const.KEY_PREF_NOTIFY_SEARCH)) {
mNotifyFix = sharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_FIX, mNotifyFix);
mNotifySearch = sharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_SEARCH, mNotifySearch);
if (!(mNotifyFix || mNotifySearch)) {
stopSelf();
} else
requestPermissions();
} else if (key.equals(Const.KEY_PREF_UNIT_TYPE)) {
prefUnitType = sharedPreferences.getBoolean(Const.KEY_PREF_UNIT_TYPE, prefUnitType);
} else if (key.equals(Const.KEY_PREF_KNOTS)) {
prefKnots = sharedPreferences.getBoolean(Const.KEY_PREF_KNOTS, prefKnots);
} else if (key.equals(Const.KEY_PREF_COORD)) {
prefCoord = Integer.valueOf(sharedPreferences.getString(Const.KEY_PREF_COORD, Integer.toString(prefCoord)));
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
prefUnitType = mSharedPreferences.getBoolean(Const.KEY_PREF_UNIT_TYPE, prefUnitType);
prefKnots = mSharedPreferences.getBoolean(Const.KEY_PREF_KNOTS, prefKnots);
prefCoord = Integer.valueOf(mSharedPreferences.getString(Const.KEY_PREF_COORD, Integer.toString(prefCoord)));
mNotifyFix = mSharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_FIX, mNotifyFix);
mNotifySearch = mSharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_SEARCH, mNotifySearch);
if (mLocationManager.getAllProviders().indexOf(LocationManager.PASSIVE_PROVIDER) >= 0) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
requestLocationUpdates();
else
Log.w("PasvLocListenerService", "ACCESS_FINE_LOCATION permission not granted. Data display will not be available.");
} else {
Log.w("PasvLocListenerService", "No passive location provider found. Data display will not be available.");
}
mBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_notify_location)
.setContentTitle(getString(R.string.value_none))
.setContentText(getString(R.string.value_none))
.setWhen(0)
.setVisibility(Notification.VISIBILITY_PUBLIC);
Intent mainIntent = new Intent(this, MainActivity.class);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(mainIntent);
PendingIntent mainPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
mBuilder.setContentIntent(mainPendingIntent);
// if we were started through a broadcast, mGpsStatusReceiver had
// no way of picking it up, so we need to forward it manually
mGpsStatusReceiver.onReceive(this, intent);
return START_STICKY;
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
public void showStatusNoLocation() {
if (mNotifySearch && (mStatus != GPS_INACTIVE)) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
mBuilder.setSmallIcon(R.drawable.ic_stat_notify_nolocation);
mBuilder.setContentTitle(getString(R.string.notify_nolocation_title));
mBuilder.setContentText(getString(R.string.notify_nolocation_body));
mBuilder.setStyle(null);
startForeground(ONGOING_NOTIFICATION, mBuilder.build());
} else
requestPermissions();
} else {
stopForeground(true);
}
}
private void requestLocationUpdates() {
mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
mLocationManager.addGpsStatusListener(this);
}
private void requestPermissions() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Log.i("PasvLocListenerService", "ACCESS_FINE_LOCATION permission not granted, asking for it...");
// TODO proper notification content
PermissionHelper.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
Const.PERM_REQUEST_LOCATION_NOTIFICATION,
getString(R.string.notify_perm_title),
getString(R.string.notify_perm_body),
R.drawable.ic_security);
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/SatStatApplication.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat;
import android.Manifest;
import android.app.Application;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback;
import android.util.Log;
public class SatStatApplication extends Application implements OnRequestPermissionsResultCallback {
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
boolean isGranted = false;
for (int i = 0; i < grantResults.length; i++)
if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION) && (grantResults[i] == PackageManager.PERMISSION_GRANTED))
isGranted = true;
if (isGranted)
sendBroadcast(new Intent(Const.AGPS_DATA_EXPIRED));
else
Log.w("PasvLocListenerService", "ACCESS_FINE_LOCATION permission not granted. AGPS data could not be updated.");
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTower.java
================================================
package com.vonglasow.michael.satstat.data;
import android.telephony.TelephonyManager;
import android.util.Log;
public abstract class CellTower {
public static final int SOURCE_CELL_LOCATION = 1;
public static final int SOURCE_NEIGHBORING_CELL_INFO = 2;
public static final int SOURCE_CELL_INFO = 4;
public static final int UNKNOWN = -1;
public static final int DBM_UNKNOWN = 85; // 99 is unknown ASU, hence 99 * 2 - 113
protected int dbm = DBM_UNKNOWN;
protected int generation = 0;
protected boolean serving = false;
protected int source = 0;
/**
* Returns the alternate cell identity in text form.
*
* The alternate cell identity is an alternate identifier, apart from the
* globally unique cell identifier, which can be used to identify the cell.
*
* Subclasses for network families that use alternate identifiers must
* override this method to provide a string in the following form:
*
* {@code network:text-id[-id]*}
*
* {@code network} is a string which uniquely identifies the network family.
* It is followed by a colon and a {@code text} which marks the identifier
* as an alternate identifier, a dash and a sequence of {@code id}s in
* hierarchical order (top to bottom), separated by dashes. Leading zeroes
* are stripped from {@code id}s. The {@code id} structure is specific to
* the network family.
*
* Network families that do not use alternate identifiers should inherit
* the default implementation, which returns {@code null}.
*/
public String getAltText() {
return null;
}
public int getDbm() {
return dbm;
}
public int getGeneration() {
return generation;
}
/**
* Returns the network generation of a phone network type.
* @param networkType The network type as returned by {@link TelephonyManager.getNetworkType}
* @return 2, 3 or 4 for 2G, 3G or 4G; 0 for unknown
*/
public static int getGenerationFromNetworkType(int networkType) {
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_CDMA:
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_GPRS:
case TelephonyManager.NETWORK_TYPE_IDEN:
return 2;
case TelephonyManager.NETWORK_TYPE_1xRTT:
case TelephonyManager.NETWORK_TYPE_EHRPD:
case TelephonyManager.NETWORK_TYPE_EVDO_0:
case TelephonyManager.NETWORK_TYPE_EVDO_A:
case TelephonyManager.NETWORK_TYPE_EVDO_B:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
case TelephonyManager.NETWORK_TYPE_HSPAP:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_UMTS:
return 3;
case TelephonyManager.NETWORK_TYPE_LTE:
return 4;
default:
return 0;
}
}
/**
* Returns the cell identity in text form.
*
* Subclasses must override this method to provide a string in the following form:
*
* {@code network:id[-id]*}
*
* {@code network} is a string which uniquely identifies the network family.
* It is followed by a colon and a sequence of {@code id}s in hierarchical
* order (top to bottom), separated by dashes. Leading zeroes are stripped
* from {@code id}s. The {@code id} structure is specific to the network
* family.
*/
public abstract String getText();
/**
* Whether the cell was included in the last update from any of the sources.
*
* When an update is received from a source, cells that were received in an
* earlier update from the same source have the flag for that source reset
* but are still kept in the list until the next update. Such cells should
* be considered stale and not be displayed in any list of active cells.
* @return {@code true} if the cell has its flag for at least one source set, {@code false} if not
*/
public boolean hasSource() {
return (source >= 0);
}
public boolean isCellInfo() {
return ((source & SOURCE_CELL_INFO) == SOURCE_CELL_INFO);
}
public boolean isCellLocation() {
return ((source & SOURCE_CELL_LOCATION) == SOURCE_CELL_LOCATION);
}
public boolean isNeighboringCellInfo() {
return ((source & SOURCE_NEIGHBORING_CELL_INFO) == SOURCE_NEIGHBORING_CELL_INFO);
}
/**
* Whether the device is currently registered with this cell.
*
* If the cell was updated through a {@link android.telephony.CellLocation},
* this method will always return {@code true}.
*/
public boolean isServing() {
return (serving || ((this.source & SOURCE_CELL_LOCATION) != 0));
}
/**
* Determines a "loose match" for two parts of a cell ID.
*
* A "loose match" will return true if one of its two arguments is {@link #UNKNOWN}, or if both
* arguments are truly equal.
*
* Any part of a cell identification (e.g. MCC, MNC, any area ID, cell ID, scrambling code) can
* be compared in this manner as long as it assigns a value of {@link #UNKNOWN} to values which
* are not known, and only to those.
*
* @param l
* @param r
* @return True for a match, false otherwise
*/
public static boolean matches(int l, int r) {
if ((l == UNKNOWN) || (r == UNKNOWN))
return true;
return (l == r);
}
public void setCellInfo(boolean value) {
if (value)
this.source = this.source | SOURCE_CELL_INFO;
else
this.source = this.source & ~SOURCE_CELL_INFO;
}
public void setCellLocation(boolean value) {
if (value)
this.source = this.source | SOURCE_CELL_LOCATION;
else
this.source = this.source & ~SOURCE_CELL_LOCATION;
}
public void setDbm(int dbm) {
this.dbm = dbm;
}
public void setGeneration(int generation) {
if (this instanceof CellTowerLte)
Log.d(this.getClass().getSimpleName(), String.format("Setting network type to %d for cell %s (%s)", generation, this.getText(), this.getAltText()));
this.generation = generation;
}
public void setNeighboringCellInfo(boolean value) {
if (value)
this.source = this.source | SOURCE_NEIGHBORING_CELL_INFO;
else
this.source = this.source & ~SOURCE_NEIGHBORING_CELL_INFO;
}
/**
* Sets the network generation based on the phone network type.
*
* The value set here cannot be retrieved directly, but subsequent calls to
* {@link #getGeneration()} will return the corresponding generation.
* @param networkType The network type as returned by {@link TelephonyManager.getNetworkType}
*/
public void setNetworkType(int networkType) {
if (this instanceof CellTowerLte)
Log.d(this.getClass().getSimpleName(), String.format("Changing network type for cell %s (%s)", this.getText(), this.getAltText()));
this.generation = getGenerationFromNetworkType(networkType);
}
public void setServing(boolean serving) {
this.serving = serving;
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerCdma.java
================================================
package com.vonglasow.michael.satstat.data;
public class CellTowerCdma extends CellTower {
public static final String FAMILY = "cdma";
private int bsid;
private int nid;
private int sid;
public CellTowerCdma(int sid, int nid, int bsid) {
super();
this.setSid(sid);
this.setNid(nid);
this.setBsid(bsid);
}
public int getBsid() {
return bsid;
}
public int getNid() {
return nid;
}
public int getSid() {
return sid;
}
/**
* Returns the cell identity in text form.
*
* For CDMA-like networks this string has the following form:
*
* {@code cdma:sid-nid-bsid}
*
* The first is a string which uniquely identifies the network family.
* It is followed by a colon and a sequence of System ID, Network ID and
* Base Station ID, separated by dashes and with no leading zeroes.
*/
@Override
public String getText() {
return getText(sid, nid, bsid);
}
/**
* Converts a SID/NID/BSID tuple to an identity string, or {@code null}
* if BSID is invalid.
*/
public static String getText(int sid, int nid, int bsid) {
int iSid = ((sid == -1) || (sid == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : sid;
int iNid = ((nid == -1) || (nid == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : nid;
if ((bsid == -1) || (bsid == Integer.MAX_VALUE))
return null;
else
return String.format("%s:%d-%d-%d", FAMILY, iSid, iNid, bsid);
}
public void setBsid(int bsid) {
if ((bsid != Integer.MAX_VALUE) && (bsid != -1))
this.bsid = bsid;
else
this.bsid = CellTower.UNKNOWN;
}
public void setNid(int nid) {
if ((nid != Integer.MAX_VALUE) && (nid != -1))
this.nid = nid;
else
this.nid = CellTower.UNKNOWN;
}
public void setSid(int sid) {
if ((sid != Integer.MAX_VALUE) && (sid != -1))
this.sid = sid;
else
this.sid = CellTower.UNKNOWN;
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerGsm.java
================================================
package com.vonglasow.michael.satstat.data;
public class CellTowerGsm extends CellTower {
public static final String ALT_ID = "pci";
public static final String FAMILY = "gsm";
public static final int MAX_2G_CID = 65535;
private int cid;
private int lac;
private int mcc;
private int mnc;
private int psc;
public CellTowerGsm(int mcc, int mnc, int lac, int cid, int psc) {
super();
this.setMcc(mcc);
this.setMnc(mnc);
this.setLac(lac);
this.setCid(cid);
this.setPsc(psc);
}
/**
* Returns the alternate cell identity in text form.
*
* For UMTS networks this string has the following form:
*
* {@code gsm:psc-nnn}
*
* The first is a string which uniquely identifies the network family.
* It is followed by a colon, the string {@code psc} to denote an alternate
* cell identity, a dash and the primary scrambling code (PSC) with no
* leading zeroes.
*
* A result is only returned for UMTS (3G) cells with a valid PSC. In all
* other cases, {@code null} is returned.
*/
public String getAltText() {
return getAltText(this.psc);
}
/**
* Converts a PSC to an alternate identity string, or {@code null} if the
* PSC is invalid.
*/
public static String getAltText(int psc) {
if ((psc == CellTower.UNKNOWN) || (psc == Integer.MAX_VALUE))
return null;
return String.format("%s:%s-%d", FAMILY, ALT_ID, psc);
}
public int getCid() {
return this.cid;
}
public int getLac() {
return this.lac;
}
public int getMcc() {
return this.mcc;
}
public int getMnc() {
return this.mnc;
}
public int getPsc() {
return this.psc;
}
/**
* Returns the cell identity in text form.
*
* For GSM-like networks this string has the following form:
*
* {@code gsm:mcc-mnc-lac-cid}
*
* The first is a string which uniquely identifies the network family.
* It is followed by a colon and a sequence of country code, network code,
* Local Area Code and Cell ID, separated by dashes and with no leading
* zeroes.
*/
@Override
public String getText() {
return getText(mcc, mnc, lac, cid);
}
/**
* Converts a MCC/MNC/LAC/CID tuple to an identity string, or
* {@code null} if CID is invalid.
*/
public static String getText(int mcc, int mnc, int lac, int cid) {
int iMcc = ((mcc == -1) || (mcc == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : mcc;
int iMnc = ((mnc == -1) || (mnc == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : mnc;
int iLac = ((lac == -1) || (lac == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : lac;
if ((cid == -1) || (cid == Integer.MAX_VALUE))
return null;
else
return String.format("%s:%d-%d-%d-%d", FAMILY, iMcc, iMnc, iLac, cid);
}
/**
* Sets signal strength dBm based on ASU.
*
* ASU can be converted into dBm with the formula:
* {@code dBm = -113 + 2 * asu}. The reporting range is from -113 dBm to
* -51 dBm (0 to 31). Values outside this range will be ignored. Refer to
* 3GPP TS 27.007 (Ver 10.3.0) Sec 8.69
*/
// or 3GPP TS 27.007 8.5
public void setAsu(int asu){
if ((asu >= 0) || (asu <= 31))
this.setDbm(-113 + 2 * asu);
}
public void setCid(int cid) {
if ((cid != Integer.MAX_VALUE) && (cid != -1))
this.cid = cid;
else
this.cid = CellTower.UNKNOWN;
if (this.cid > MAX_2G_CID)
this.generation = 3;
}
/**
* Sets signal strength dBm based on CPICH RSCP.
*
* RSCP is Received Signal Code Power. This value can be converted into dBm
* with the formula: {@code dBm = rscp - 116}. The reporting range for
* CPICH RSCP is from -120 dBm to -25 dBm (-5 to 91, with the two extremes
* indicating any RSCP outside the reporting range). Values outside this
* range will be ignored. Refer to 3GPP TS 25.133 (Ver 10.2.0) 9.1.1.3.
*
* Note: It seems that at least on some devices
* {@link android.telephony.NeighboringCellInfo#getRssi()} returns RSCP in
* dBm rather than the index specified in the document. This method
* attempts to guess the unit and set signal strength to the correct value
* in either case. See
* http://stackoverflow.com/questions/26620378/android-neighboringcellinfo-getrssi-returning-weird-data-for-umts-cells
*/
public void setCpichRscp(int rscp){
if ((rscp >= -5) && (rscp <= 91))
this.setDbm(rscp - 116);
else if ((rscp >= -121) && (rscp <=-25))
this.setDbm(rscp);
}
public void setLac(int lac) {
if ((lac != Integer.MAX_VALUE) && (lac != -1))
this.lac = lac;
else
this.lac = CellTower.UNKNOWN;
}
public void setMcc(int mcc) {
if ((mcc != Integer.MAX_VALUE) && (mcc != -1))
this.mcc = mcc;
else
this.mcc = CellTower.UNKNOWN;
}
public void setMnc(int mnc) {
if ((mcc != Integer.MAX_VALUE) && (mcc != -1))
this.mnc = mnc;
else
this.mnc = CellTower.UNKNOWN;
}
public void setPsc(int psc) {
if ((psc != Integer.MAX_VALUE) && (psc != -1)) {
this.psc = psc;
this.generation = 3;
} else
this.psc = CellTower.UNKNOWN;
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerList.java
================================================
package com.vonglasow.michael.satstat.data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
public abstract class CellTowerList extends HashMap {
/**
* Returns all entries in the list.
*
* This method returns all entries in the list, with duplicates eliminated.
* It is preferred over {@link #values()}, which may return duplicates.
* @return
*/
public Set getAll() {
Set result = new HashSet(this.values());
return result;
}
/**
* Removes cells of the specified source.
*
* This method clears the flags corresponding to {@code source} in the
* internal source field of all entries, and removes entries whose source
* field is null. Call this method prior to adding new data from a source,
* to tell the list that any cell information previously supplied by this
* source is no longer current.
* @param source Any combination of
* {@link com.michael.vonglasow.satstat.data.CellTower#SOURCE_CELL_LOCATION},
* {@link com.michael.vonglasow.satstat.data.CellTower#SOURCE_NEIGHBORING_CELL_INFO}
* or {@link com.michael.vonglasow.satstat.data.CellTower#SOURCE_CELL_INFO}.
*/
public void removeSource(int source) {
ArrayList toDelete = new ArrayList();
for (String entry : this.keySet()) {
CellTower ct = this.get(entry);
ct.source = ct.source & ~source;
if (ct.source == 0)
toDelete.add(entry);
}
for (String entry : toDelete)
this.remove(entry);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerListCdma.java
================================================
package com.vonglasow.michael.satstat.data;
import java.util.List;
import android.annotation.TargetApi;
import android.os.Build;
import android.telephony.CellIdentityCdma;
import android.telephony.CellInfo;
import android.telephony.CellInfoCdma;
import android.telephony.cdma.CdmaCellLocation;
public class CellTowerListCdma extends CellTowerList {
/**
* Returns the cell tower with the specified data, or {@code null} if it is not in the list.
*/
public CellTowerCdma get(int sid, int nid, int bsid) {
String entry = CellTowerCdma.getText(sid, nid, bsid);
if (entry == null)
return null;
else
return this.get(entry);
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, it is replaced; if not, a new
* entry is created.
*
* This method will set the cell's identity data. After this call,
* {@link #isServing()} will return {@code true} for this cell.
* @return The new or updated entry.
*/
public CellTowerCdma update(CdmaCellLocation location) {
CellTowerCdma result = this.get(location.getSystemId(), location.getNetworkId(), location.getBaseStationId());
if (result == null) {
result = new CellTowerCdma(location.getSystemId(), location.getNetworkId(), location.getBaseStationId());
this.put(result.getText(), result);
}
result.setCellLocation(true);
return result;
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, it is replaced; if not, a new
* entry is created.
*
* This method will set the cell's identity data, its signal strength and
* whether it is the currently serving cell. If the API level is 18 or
* higher, it will also set the generation.
* @return The new or updated entry.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public CellTowerCdma update(CellInfoCdma cell) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return null;
CellIdentityCdma cid = cell.getCellIdentity();
CellTowerCdma result = this.get(cid.getSystemId(), cid.getNetworkId(), cid.getBasestationId());
if (result == null) {
result = new CellTowerCdma(cid.getSystemId(), cid.getNetworkId(), cid.getBasestationId());
this.put(result.getText(), result);
}
result.setCellInfo(true);
result.setDbm(cell.getCellSignalStrength().getDbm());
result.setServing(cell.isRegistered());
return result;
}
/**
* Adds or updates a list of cell towers.
*
* This method first calls {@link #removeSource(int)} with
* {@link com.vonglasow.michael.satstat.data.CellTower#SOURCE_CELL_INFO} as
* its argument. Then it iterates through all entries in {@code cells} and
* updates each entry that is of type {@link android.telephony.CellInfoCdma}
* by calling {@link #update(CellInfoCdma)}, passing that entry as the
* argument.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public void updateAll(List cells) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return;
this.removeSource(CellTower.SOURCE_CELL_INFO);
if (cells == null)
return;
for (CellInfo cell : cells)
if (cell instanceof CellInfoCdma)
this.update((CellInfoCdma) cell);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerListGsm.java
================================================
package com.vonglasow.michael.satstat.data;
import java.util.List;
import android.annotation.TargetApi;
import android.os.Build;
import android.telephony.CellIdentityGsm;
import android.telephony.CellIdentityWcdma;
import android.telephony.CellInfo;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoWcdma;
import android.telephony.NeighboringCellInfo;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;
public class CellTowerListGsm extends CellTowerList {
/**
* Returns the cell tower with the specified data, or {@code null} if it is not in the list.
*/
public CellTowerGsm get(int psc) {
String entry = CellTowerGsm.getAltText(psc);
if (entry == null)
return null;
else
return this.get(entry);
}
/**
* Returns the cell tower with the specified data, or {@code null} if it is not in the list.
*/
public CellTowerGsm get(int mcc, int mnc, int lac, int cid) {
String entry = CellTowerGsm.getText(mcc, mnc, lac, cid);
if (entry == null)
return null;
else
return this.get(entry);
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created.
*
* This method will set the cell's identity data. After this call,
* {@link #isServing()} will return {@code true} for this cell.
* @param networkOperator The network operator, as returned by {@link android.telephony.TelephonyManager#getNetworkOperator()}.
* @param location The {@link android.telephony.GsmCellLocation}, as returned by {@link android.telephony.TelephonyManager#getCellLocation()}.
* @return The new or updated entry.
*/
public CellTowerGsm update(String networkOperator, GsmCellLocation location) {
int mcc = CellTower.UNKNOWN;
int mnc = CellTower.UNKNOWN;
if (networkOperator.length() > 3) {
mcc = Integer.parseInt(networkOperator.substring(0, 3));
mnc = Integer.parseInt(networkOperator.substring(3));
}
CellTowerGsm result = null;
CellTowerGsm cand = this.get(mcc, mnc, location.getLac(), location.getCid());
if ((cand != null) && CellTower.matches(location.getPsc(), cand.getPsc()))
result = cand;
if (result == null) {
cand = this.get(location.getPsc());
if ((cand != null)
&& CellTower.matches(mcc, cand.getMcc())
&& CellTower.matches(mnc, cand.getMnc())
&& CellTower.matches(location.getLac(), cand.getLac())
&& CellTower.matches(location.getCid(), cand.getCid()))
result = cand;
}
if (result == null)
result = new CellTowerGsm(mcc, mnc, location.getLac(), location.getCid(), location.getPsc());
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(mcc);
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(mnc);
if (result.getLac() == CellTower.UNKNOWN)
result.setLac(location.getLac());
if (result.getCid() == CellTower.UNKNOWN)
result.setCid(location.getCid());
if (result.getPsc() == CellTower.UNKNOWN)
result.setPsc(location.getPsc());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
if ((result.getText() == null) && (result.getAltText() == null))
Log.d(this.getClass().getSimpleName(), String.format("Added %d G cell with no data from GsmCellLocation", result.getGeneration()));
result.setCellLocation(true);
return result;
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created. Cells whose network type is not a flavor of GSM or
* UMTS will be rejected.
*
* This method will set the cell's identity data, generation and its signal
* strength.
* @return The new or updated entry, or {@code null} if the cell was rejected
*/
public CellTowerGsm update(String networkOperator, NeighboringCellInfo cell) {
int mcc = CellTower.UNKNOWN;
int mnc = CellTower.UNKNOWN;
if (networkOperator.length() > 3) {
mcc = Integer.parseInt(networkOperator.substring(0, 3));
mnc = Integer.parseInt(networkOperator.substring(3));
}
CellTowerGsm result = null;
CellTowerGsm cand = this.get(mcc, mnc, cell.getLac(), cell.getCid());
if ((cand != null) && CellTower.matches(cell.getPsc(), cand.getPsc()))
result = cand;
if (result == null) {
cand = this.get(cell.getPsc());
if ((cand != null)
&& CellTower.matches(mcc, cand.getMcc())
&& CellTower.matches(mnc, cand.getMnc())
&& CellTower.matches(cell.getLac(), cand.getLac())
&& CellTower.matches(cell.getCid(), cand.getCid()))
result = cand;
}
if (result == null)
result = new CellTowerGsm(mcc, mnc, cell.getLac(), cell.getCid(), cell.getPsc());
result.setNeighboringCellInfo(true);
int networkType = cell.getNetworkType();
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_UMTS:
case TelephonyManager.NETWORK_TYPE_HSDPA:
case TelephonyManager.NETWORK_TYPE_HSUPA:
case TelephonyManager.NETWORK_TYPE_HSPA:
/*
* for details see TS 25.133 section 9.1.1.3
* http://www.3gpp.org/DynaReport/25133.htm
*/
result.setCpichRscp(cell.getRssi());
break;
case TelephonyManager.NETWORK_TYPE_EDGE:
case TelephonyManager.NETWORK_TYPE_GPRS:
result.setAsu(cell.getRssi());
break;
default:
// not a GSM or UMTS cell, return
return null;
// result.setDbm(CellTower.DBM_UNKNOWN);
// not needed because this is the default value; setting it
// here might overwrite valid data obtained from a different
// source
}
result.setNetworkType(networkType);
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(mcc);
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(mnc);
if (result.getLac() == CellTower.UNKNOWN)
result.setLac(cell.getLac());
if (result.getCid() == CellTower.UNKNOWN)
result.setCid(cell.getCid());
if (result.getPsc() == CellTower.UNKNOWN)
result.setPsc(cell.getPsc());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
if ((result.getText() == null) && (result.getAltText() == null))
Log.d(this.getClass().getSimpleName(), String.format("Added %d G cell with no data from NeighboringCellInfo", result.getGeneration()));
return result;
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created.
*
* This method will set the cell's identity data, its signal strength and
* whether it is the currently serving cell. If the API level is 18 or
* higher, it will also set the generation.
* @return The new or updated entry.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public CellTowerGsm update(CellInfoGsm cell) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return null;
CellIdentityGsm cid = cell.getCellIdentity();
CellTowerGsm result = null;
CellTowerGsm cand = this.get(cid.getMcc(), cid.getMnc(), cid.getLac(), cid.getCid());
if ((cand != null) && CellTower.matches(cid.getPsc(), cand.getPsc()))
result = cand;
if (result == null) {
cand = this.get(cid.getPsc());
if ((cand != null)
&& ((cid.getMcc() == Integer.MAX_VALUE) || CellTower.matches(cid.getMcc(), cand.getMcc()))
&& ((cid.getMnc() == Integer.MAX_VALUE) || CellTower.matches(cid.getMnc(), cand.getMnc()))
&& ((cid.getLac() == Integer.MAX_VALUE) || CellTower.matches(cid.getLac(), cand.getLac()))
&& ((cid.getCid() == Integer.MAX_VALUE) ||CellTower.matches(cid.getCid(), cand.getCid())))
result = cand;
}
if (result == null)
result = new CellTowerGsm(cid.getMcc(), cid.getMnc(), cid.getLac(), cid.getCid(), cid.getPsc());
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(cid.getMcc());
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(cid.getMnc());
if (result.getLac() == CellTower.UNKNOWN)
result.setLac(cid.getLac());
if (result.getCid() == CellTower.UNKNOWN)
result.setCid(cid.getCid());
if (result.getPsc() == CellTower.UNKNOWN)
result.setPsc(cid.getPsc());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
result.setCellInfo(true);
result.setDbm(cell.getCellSignalStrength().getDbm());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
result.setGeneration(2);
result.setServing(cell.isRegistered());
if ((result.getText() == null) && (result.getAltText() == null))
Log.d(this.getClass().getSimpleName(), String.format("Added %d G cell with no data from CellInfoGsm", result.getGeneration()));
return result;
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created.
*
* This method will set the cell's identity data and generation, its signal
* strength and whether it is the currently serving cell.
* @return The new or updated entry.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public CellTowerGsm update(CellInfoWcdma cell) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
return null;
CellIdentityWcdma cid = cell.getCellIdentity();
CellTowerGsm result = null;
CellTowerGsm cand = this.get(cid.getMcc(), cid.getMnc(), cid.getLac(), cid.getCid());
if ((cand != null) && CellTower.matches(cid.getPsc(), cand.getPsc()))
result = cand;
if (result == null) {
cand = this.get(cid.getPsc());
if ((cand != null)
&& ((cid.getMcc() == Integer.MAX_VALUE) || CellTower.matches(cid.getMcc(), cand.getMcc()))
&& ((cid.getMnc() == Integer.MAX_VALUE) || CellTower.matches(cid.getMnc(), cand.getMnc()))
&& ((cid.getLac() == Integer.MAX_VALUE) || CellTower.matches(cid.getLac(), cand.getLac()))
&& ((cid.getCid() == Integer.MAX_VALUE) ||CellTower.matches(cid.getCid(), cand.getCid())))
result = cand;
}
if (result == null)
result = new CellTowerGsm(cid.getMcc(), cid.getMnc(), cid.getLac(), cid.getCid(), cid.getPsc());
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(cid.getMcc());
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(cid.getMnc());
if (result.getLac() == CellTower.UNKNOWN)
result.setLac(cid.getLac());
if (result.getCid() == CellTower.UNKNOWN)
result.setCid(cid.getCid());
if (result.getPsc() == CellTower.UNKNOWN)
result.setPsc(cid.getPsc());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
result.setCellInfo(true);
result.setDbm(cell.getCellSignalStrength().getDbm());
result.setGeneration(3);
result.setServing(cell.isRegistered());
if ((result.getText() == null) && (result.getAltText() == null))
Log.d(this.getClass().getSimpleName(), String.format("Added %d G cell with no data from CellInfoWcdma", result.getGeneration()));
return result;
}
/**
* Adds or updates a list of cell towers.
*
* This method first calls {@link #removeSource(int)} with
* {@link com.vonglasow.michael.satstat.data.CellTower#SOURCE_CELL_INFO} as
* its argument. Then it iterates through all entries in {@code cells} and
* updates each entry that is of type {@link android.telephony.CellInfoGsm}
* or {@link android.telephony.CellInfoWcdma} by calling
* {@link #update(CellInfoGsm)} or {@link #update(CellInfoWcdma)}
* (depending on type), passing that entry as the argument.
*/
public void updateAll(List cells) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return;
this.removeSource(CellTower.SOURCE_CELL_INFO);
if (cells == null)
return;
for (CellInfo cell : cells)
if (cell instanceof CellInfoGsm)
this.update((CellInfoGsm) cell);
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2)
if (cell instanceof CellInfoWcdma)
this.update((CellInfoWcdma) cell);
}
/**
* Adds or updates a list of cell towers.
*
* This method first calls {@link #removeSource(int)} with
* {@link com.vonglasow.michael.satstat.data.CellTower#SOURCE_NEIGHBORING_CELL_INFO}
* as its argument. Then it iterates through all entries in {@code cells}
* and updates each entry by calling {@link #update(NeighboringCellInfo)},
* passing that entry as the argument.
*/
public void updateAll(String networkOperator, List cells) {
this.removeSource(CellTower.SOURCE_NEIGHBORING_CELL_INFO);
if (cells != null)
for (NeighboringCellInfo cell : cells)
this.update(networkOperator, cell);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerListLte.java
================================================
package com.vonglasow.michael.satstat.data;
import java.util.List;
import android.annotation.TargetApi;
import android.os.Build;
import android.telephony.CellIdentityLte;
import android.telephony.CellInfo;
import android.telephony.CellInfoLte;
import android.telephony.NeighboringCellInfo;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;
public class CellTowerListLte extends CellTowerList {
/**
* Returns the cell tower with the specified data, or {@code null} if it is not in the list.
*/
public CellTowerLte get(int pci) {
String entry = CellTowerLte.getAltText(pci);
if (entry == null)
return null;
else
return this.get(entry);
}
/**
* Returns the cell tower with the specified data, or {@code null} if it is not in the list.
*/
public CellTowerLte get(int mcc, int mnc, int tac, int ci) {
String entry = CellTowerLte.getText(mcc, mnc, tac, ci);
if (entry == null)
return null;
else
return this.get(entry);
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created.
*
* This method will set the cell's identity data. After this call,
* {@link #isServing()} will return {@code true} for this cell.
* @param networkOperator The network operator, as returned by {@link android.telephony.TelephonyManager#getNetworkOperator()}.
* @param location The {@link android.telephony.GsmCellLocation}, as returned by {@link android.telephony.TelephonyManager#getCellLocation()}.
* @return The new or updated entry.
*/
public CellTowerLte update(String networkOperator, GsmCellLocation location) {
int mcc = CellTower.UNKNOWN;
int mnc = CellTower.UNKNOWN;
if (networkOperator.length() > 3) {
mcc = Integer.parseInt(networkOperator.substring(0, 3));
mnc = Integer.parseInt(networkOperator.substring(3));
}
CellTowerLte result = null;
CellTowerLte cand = this.get(mcc, mnc, location.getLac(), location.getCid());
if ((cand != null) && CellTower.matches(location.getPsc(), cand.getPci()))
result = cand;
if (result == null) {
cand = this.get(location.getPsc());
if ((cand != null)
&& CellTower.matches(mcc, cand.getMcc())
&& CellTower.matches(mnc, cand.getMnc())
&& CellTower.matches(location.getLac(), cand.getTac())
&& CellTower.matches(location.getCid(), cand.getCi()))
result = cand;
}
if (result == null)
result = new CellTowerLte(mcc, mnc, location.getLac(), location.getCid(), location.getPsc());
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(mcc);
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(mnc);
if (result.getTac() == CellTower.UNKNOWN)
result.setTac(location.getLac());
if (result.getCi() == CellTower.UNKNOWN)
result.setCi(location.getCid());
if (result.getPci() == CellTower.UNKNOWN)
result.setPci(location.getPsc());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
result.setCellLocation(true);
Log.d(this.getClass().getSimpleName(), String.format("Added GsmCellLocation for %s, %d G", result.getText(), result.getGeneration()));
return result;
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created. Cells whose network type is not LTE will be
* rejected.
*
* This method will set the cell's identity data, generation and its signal
* strength.
* @return The new or updated entry, or {@code null} if the cell was rejected
*/
public CellTowerLte update(String networkOperator, NeighboringCellInfo cell) {
int mcc = CellTower.UNKNOWN;
int mnc = CellTower.UNKNOWN;
if (networkOperator.length() > 3) {
mcc = Integer.parseInt(networkOperator.substring(0, 3));
mnc = Integer.parseInt(networkOperator.substring(3));
}
CellTowerLte result = null;
CellTowerLte cand = this.get(mcc, mnc, cell.getLac(), cell.getCid());
if ((cand != null) && CellTower.matches(cell.getPsc(), cand.getPci()))
result = cand;
if (result == null) {
cand = this.get(cell.getPsc());
if ((cand != null)
&& CellTower.matches(mcc, cand.getMcc())
&& CellTower.matches(mnc, cand.getMnc())
&& CellTower.matches(cell.getLac(), cand.getTac())
&& CellTower.matches(cell.getCid(), cand.getCi()))
result = cand;
}
if (result == null)
result = new CellTowerLte(mcc, mnc, cell.getLac(), cell.getCid(), cell.getPsc());
result.setNeighboringCellInfo(true);
int networkType = cell.getNetworkType();
switch (networkType) {
case TelephonyManager.NETWORK_TYPE_LTE:
result.setAsu(cell.getRssi());
break;
default:
// not an LTE cell, return
return null;
}
result.setNetworkType(networkType);
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(mcc);
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(mnc);
if (result.getTac() == CellTower.UNKNOWN)
result.setTac(cell.getLac());
if (result.getCi() == CellTower.UNKNOWN)
result.setCi(cell.getCid());
if (result.getPci() == CellTower.UNKNOWN)
result.setPci(cell.getPsc());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
Log.d(this.getClass().getSimpleName(), String.format("Added NeighboringCellInfo for %s, %d G, %d dBm",
result.getText(),
result.getGeneration(),
result.getDbm()));
return result;
}
/**
* Adds or updates a cell tower.
*
* If the cell tower is already in the list, its data is updated; if not, a
* new entry is created.
*
* This method will set the cell's identity data, its signal strength and
* whether it is the currently serving cell.
* @return The new or updated entry.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public CellTowerLte update(CellInfoLte cell) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return null;
CellIdentityLte cid = cell.getCellIdentity();
CellTowerLte result = null;
CellTowerLte cand = this.get(cid.getMcc(), cid.getMnc(), cid.getTac(), cid.getCi());
if ((cand != null) && CellTower.matches(cid.getPci(), cand.getPci()))
result = cand;
if (result == null) {
cand = this.get(cid.getPci());
if ((cand != null)
&& ((cid.getMcc() == Integer.MAX_VALUE) || CellTower.matches(cid.getMcc(), cand.getMcc()))
&& ((cid.getMnc() == Integer.MAX_VALUE) || CellTower.matches(cid.getMnc(), cand.getMnc()))
&& ((cid.getTac() == Integer.MAX_VALUE) || CellTower.matches(cid.getTac(), cand.getTac()))
&& ((cid.getCi() == Integer.MAX_VALUE) ||CellTower.matches(cid.getCi(), cand.getCi())))
result = cand;
}
if (result == null)
result = new CellTowerLte(cid.getMcc(), cid.getMnc(), cid.getTac(), cid.getCi(), cid.getPci());
if (result.getMcc() == CellTower.UNKNOWN)
result.setMcc(cid.getMcc());
if (result.getMnc() == CellTower.UNKNOWN)
result.setMnc(cid.getMnc());
if (result.getTac() == CellTower.UNKNOWN)
result.setTac(cid.getTac());
if (result.getCi() == CellTower.UNKNOWN)
result.setCi(cid.getCi());
if (result.getPci() == CellTower.UNKNOWN)
result.setPci(cid.getPci());
this.put(result.getText(), result);
this.put(result.getAltText(), result);
result.setCellInfo(true);
result.setDbm(cell.getCellSignalStrength().getDbm());
result.setServing(cell.isRegistered());
Log.d(this.getClass().getSimpleName(), String.format("Added CellInfoLte for %s, %d G, %d dBm",
result.getText(),
result.getGeneration(),
result.getDbm()));
return result;
}
/**
* Adds or updates a list of cell towers.
*
* This method first calls {@link #removeSource(int)} with
* {@link com.vonglasow.michael.satstat.data.CellTower#SOURCE_CELL_INFO} as
* its argument. Then it iterates through all entries in {@code cells} and
* updates each entry that is of type {@link android.telephony.CellInfoLte}
* by calling {@link #update(CellInfoLte)}, passing that entry as the
* argument.
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public void updateAll(List cells) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return;
this.removeSource(CellTower.SOURCE_CELL_INFO);
if (cells == null)
return;
for (CellInfo cell : cells)
if (cell instanceof CellInfoLte)
this.update((CellInfoLte) cell);
}
/**
* Adds or updates a list of cell towers.
*
* This method first calls {@link #removeSource(int)} with
* {@link com.vonglasow.michael.satstat.data.CellTower#SOURCE_NEIGHBORING_CELL_INFO}
* as its argument. Then it iterates through all entries in {@code cells}
* and updates each entry by calling {@link #update(NeighboringCellInfo)},
* passing that entry as the argument.
*/
public void updateAll(String networkOperator, List cells) {
this.removeSource(CellTower.SOURCE_NEIGHBORING_CELL_INFO);
if (cells != null)
for (NeighboringCellInfo cell : cells)
this.update(networkOperator, cell);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/data/CellTowerLte.java
================================================
package com.vonglasow.michael.satstat.data;
public class CellTowerLte extends CellTower {
public static final String ALT_ID = "pci";
public static final String FAMILY = "lte";
private int ci;
private int tac;
private int mcc;
private int mnc;
private int pci;
public CellTowerLte(int mcc, int mnc, int tac, int ci, int pci) {
super();
this.setMcc(mcc);
this.setMnc(mnc);
this.setTac(tac);
this.setCi(ci);
this.setPci(pci);
this.generation = 4;
}
/**
* Returns the alternate cell identity in text form.
*
* For UMTS networks this string has the following form:
*
* {@code lte:pci-nnn}
*
* The first is a string which uniquely identifies the network family.
* It is followed by a colon, the string {@code pci} to denote an alternate
* cell identity, a dash and the physical cell identity (PCI) with no
* leading zeroes.
*
* A result is only returned for cells with a valid PCI. In all other
* cases, {@code null} is returned.
*/
public String getAltText() {
return getAltText(this.pci);
}
/**
* Converts a PSC to an alternate identity string, or {@code null} if the
* PSC is invalid.
*/
public static String getAltText(int pci) {
if ((pci == CellTower.UNKNOWN) || (pci == Integer.MAX_VALUE))
return null;
return String.format("%s:%s-%d", FAMILY, ALT_ID, pci);
}
public int getCi() {
return this.ci;
}
public int getTac() {
return this.tac;
}
public int getMcc() {
return this.mcc;
}
public int getMnc() {
return this.mnc;
}
public int getPci() {
return this.pci;
}
/**
* Returns the cell identity in text form.
*
* For LTE networks this string has the following form:
*
* {@code lte:mcc-mnc-tac-ci}
*
* The first is a string which uniquely identifies the network family.
* It is followed by a colon and a sequence of country code, network code,
* Tracking Area Code and Cell ID, separated by dashes and with no leading
* zeroes.
*/
@Override
public String getText() {
return getText(mcc, mnc, tac, ci);
}
/**
* Converts a MCC/MNC/TAC/CI tuple to an identity string, or
* {@code null} if CI is invalid.
*/
public static String getText(int mcc, int mnc, int tac, int ci) {
int iMcc = ((mcc == -1) || (mcc == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : mcc;
int iMnc = ((mnc == -1) || (mnc == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : mnc;
int iTac = ((tac == -1) || (tac == Integer.MAX_VALUE)) ? CellTower.UNKNOWN : tac;
if ((ci == -1) || (ci == Integer.MAX_VALUE))
return null;
else
return String.format("%s:%d-%d-%d-%d", FAMILY, iMcc, iMnc, iTac, ci);
}
/**
* Sets signal strength dBm based on ASU.
*
* ASU can be converted into dBm with the formula:
* {@code dBm = -113 + 2 * asu}. The reporting range is from -113 dBm to
* -51 dBm (0 to 31). Values outside this range will be ignored. Refer to
* 3GPP TS 27.007 (Ver 10.3.0) Sec 8.69
*/
// or 3GPP TS 27.007 8.5
public void setAsu(int asu){
if ((asu >= 0) || (asu <= 31))
this.setDbm(-113 + 2 * asu);
}
public void setCi(int ci) {
if (ci != Integer.MAX_VALUE)
this.ci = ci;
else
this.ci = CellTower.UNKNOWN;
}
public void setTac(int tac) {
if (tac != Integer.MAX_VALUE)
this.tac = tac;
else
this.tac = CellTower.UNKNOWN;
}
public void setMcc(int mcc) {
if (mcc != Integer.MAX_VALUE)
this.mcc = mcc;
else
this.mcc = CellTower.UNKNOWN;
}
public void setMnc(int mnc) {
if (mnc != Integer.MAX_VALUE)
this.mnc = mnc;
else
this.mnc = CellTower.UNKNOWN;
}
public void setPci(int pci) {
if ((pci != Integer.MAX_VALUE) && (pci != -1)) {
this.pci = pci;
} else
this.pci = CellTower.UNKNOWN;
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/AboutActivity.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.view.MenuItem;
import android.widget.TextView;
import android.content.pm.PackageManager;
import com.vonglasow.michael.satstat.R;
public class AboutActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_about);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
TextView aboutBuild = (TextView) findViewById(R.id.aboutBuild);
InputStream buildInStream = getResources().openRawResource(R.raw.build);
ByteArrayOutputStream buildOutStream = new ByteArrayOutputStream();
int i;
try {
i = buildInStream.read();
while (i != -1) {
if (i >= 32) buildOutStream.write(i);
i = buildInStream.read();
}
buildInStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
aboutBuild.setText(String.format("%s %s (%s)", this.getString(R.string.about_version), getPackageManager().getPackageInfo(getPackageName(), 0).versionName, buildOutStream.toString()));
} catch(PackageManager.NameNotFoundException e) {
aboutBuild.setText(buildOutStream.toString());
}
TextView aboutText = (TextView) findViewById(R.id.aboutText);
aboutText.setText(Html.fromHtml(this.getString(R.string.about_text)));
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/GpsSectionFragment.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import uk.me.jstott.jcoord.LatLng;
import uk.me.jstott.jcoord.MGRSRef;
import com.hzi.UTM;
import android.hardware.GeomagneticField;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.R;
import com.vonglasow.michael.satstat.widgets.GpsSnrView;
import com.vonglasow.michael.satstat.widgets.GpsStatusView;
/**
* The fragment which displays GPS data.
*/
public class GpsSectionFragment extends Fragment {
public static final String TAG = "GpsSectionFragment";
/**
* The fragment argument representing the section number for this
* fragment.
*/
public static final String ARG_SECTION_NUMBER = "section_number";
private MainActivity mainActivity = null;
private DateFormat df;
private LinearLayout gpsRootLayout;
private GpsStatusView gpsStatusView;
private GpsSnrView gpsSnrView;
private LinearLayout gpsLatLayout;
private TextView gpsLat;
private LinearLayout gpsLonLayout;
private TextView gpsLon;
private LinearLayout gpsCoordLayout;
private TextView gpsCoord;
private TextView orDeclination;
private TextView gpsSpeed;
private TextView gpsSpeedUnit;
private TextView gpsAlt;
private TextView gpsAltUnit;
private TextView gpsTime;
private TextView gpsBearing;
private TextView gpsAccuracy;
private TextView gpsAccuracyUnit;
private TextView gpsOrientation;
private TextView gpsSats;
private TextView gpsTtff;
/*
* Last known gravity and magnetic field vector
*/
private float[] gravity;
private float[] geomagnetic;
/* Whether the orientation sensor returns valid data */
private boolean hasOrientation = false;
public GpsSectionFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mainActivity = (MainActivity) this.getContext();
View rootView = inflater.inflate(R.layout.fragment_main_gps, container, false);
// Initialize controls
gpsRootLayout = (LinearLayout) rootView.findViewById(R.id.gpsRootLayout);
gpsSnrView = (GpsSnrView) rootView.findViewById(R.id.gpsSnrView);
gpsStatusView = new GpsStatusView(rootView.getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
params.gravity = Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL;
params.weight = 1;
gpsRootLayout.addView(gpsStatusView, 0, params);
gpsLatLayout = (LinearLayout) rootView.findViewById(R.id.gpsLatLayout);
gpsLat = (TextView) rootView.findViewById(R.id.gpsLat);
gpsLonLayout = (LinearLayout) rootView.findViewById(R.id.gpsLonLayout);
gpsLon = (TextView) rootView.findViewById(R.id.gpsLon);
gpsCoordLayout = (LinearLayout) rootView.findViewById(R.id.gpsCoordLayout);
gpsCoord = (TextView) rootView.findViewById(R.id.gpsCoord);
orDeclination = (TextView) rootView.findViewById(R.id.orDeclination);
gpsSpeed = (TextView) rootView.findViewById(R.id.gpsSpeed);
gpsSpeedUnit = (TextView) rootView.findViewById(R.id.gpsSpeedUnit);
gpsAlt = (TextView) rootView.findViewById(R.id.gpsAlt);
gpsAltUnit = (TextView) rootView.findViewById(R.id.gpsAltUnit);
gpsTime = (TextView) rootView.findViewById(R.id.gpsTime);
gpsBearing = (TextView) rootView.findViewById(R.id.gpsBearing);
gpsAccuracy = (TextView) rootView.findViewById(R.id.gpsAccuracy);
gpsAccuracyUnit = (TextView) rootView.findViewById(R.id.gpsAccuracyUnit);
gpsOrientation = (TextView) rootView.findViewById(R.id.gpsOrientation);
gpsSats = (TextView) rootView.findViewById(R.id.gpsSats);
gpsTtff = (TextView) rootView.findViewById(R.id.gpsTtff);
df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
mainActivity.gpsSectionFragment = this;
return rootView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mainActivity.gpsSectionFragment == this)
mainActivity.gpsSectionFragment = null;
}
/**
* Called by {@link MainActivity} when the status of the GPS changes. Updates GPS display.
*/
public void onGpsStatusChanged(GpsStatus status, int satsInView, int satsUsed, Iterable sats) {
gpsSats.setText(String.valueOf(satsUsed) + "/" + String.valueOf(satsInView));
gpsTtff.setText(String.valueOf(status.getTimeToFirstFix() / 1000));
gpsStatusView.showSats(sats);
gpsSnrView.showSats(sats);
}
/**
* Called by {@link MainActivity} when a new location is found by the GPS location provider.
* Stores the location and updates GPS display and map view.
*/
public void onLocationChanged(Location location) {
if (location.hasAccuracy()) {
Float getAcc = (float) 0.0;
if(mainActivity.prefUnitType) {
getAcc = (float)(location.getAccuracy());
} else {
getAcc = (float)(location.getAccuracy() * (float) 3.28084);
}
gpsAccuracy.setText(String.format("%.0f", getAcc));
gpsAccuracyUnit.setText(getString(((mainActivity.prefUnitType) ? R.string.unit_meter : R.string.unit_feet)));
} else {
gpsAccuracy.setText(getString(R.string.value_none));
gpsAccuracyUnit.setText("");
}
if (mainActivity.prefCoord == Const.KEY_PREF_COORD_DECIMAL) {
gpsCoordLayout.setVisibility(View.GONE);
gpsLatLayout.setVisibility(View.VISIBLE);
gpsLonLayout.setVisibility(View.VISIBLE);
gpsLat.setText(String.format("%.5f%s", location.getLatitude(), getString(R.string.unit_degree)));
gpsLon.setText(String.format("%.5f%s", location.getLongitude(), getString(R.string.unit_degree)));
} else if (mainActivity.prefCoord == Const.KEY_PREF_COORD_MIN) {
gpsCoordLayout.setVisibility(View.GONE);
gpsLatLayout.setVisibility(View.VISIBLE);
gpsLonLayout.setVisibility(View.VISIBLE);
double dec = location.getLatitude();
double deg = (int) dec;
double min = Math.abs(60.0 * (dec - deg));
gpsLat.setText(String.format("%.0f%s %.3f'", deg, getString(R.string.unit_degree), min + /*rounding*/ 0.0005));
dec = location.getLongitude();
deg = (int) dec;
min = Math.abs(60.0 * (dec - deg));
gpsLon.setText(String.format("%.0f%s %.3f'", deg, getString(R.string.unit_degree), min + /*rounding*/ 0.0005));
} else if (mainActivity.prefCoord == Const.KEY_PREF_COORD_SEC) {
gpsCoordLayout.setVisibility(View.GONE);
gpsLatLayout.setVisibility(View.VISIBLE);
gpsLonLayout.setVisibility(View.VISIBLE);
double dec = location.getLatitude();
double deg = (int) dec;
double tmp = Math.abs(60.0 * (dec - deg));
double min = (int) tmp;
double sec = 60.0 * (tmp - min);
gpsLat.setText(String.format("%.0f%s %.0f' %.1f\"", deg, getString(R.string.unit_degree), min, sec + /*rounding*/ 0.05));
dec = location.getLongitude();
deg = (int) dec;
tmp = Math.abs(60.0 * (dec - deg));
min = (int) tmp;
sec = 60.0 * (tmp - min);
gpsLon.setText(String.format("%.0f%s %.0f' %.1f\"", deg, getString(R.string.unit_degree), min, sec + /*rounding*/ 0.05));
} else if (mainActivity.prefCoord == Const.KEY_PREF_COORD_MGRS) {
gpsLatLayout.setVisibility(View.GONE);
gpsLonLayout.setVisibility(View.GONE);
gpsCoordLayout.setVisibility(View.VISIBLE);
gpsCoord.setText(new LatLng(location.getLatitude(), location.getLongitude()).toMGRSRef().toString(MGRSRef.PRECISION_1M));
} else if (mainActivity.prefCoord == Const.KEY_PREF_COORD_UTM) {
gpsLatLayout.setVisibility(View.GONE);
gpsLonLayout.setVisibility(View.GONE);
gpsCoordLayout.setVisibility(View.VISIBLE);
gpsCoord.setText(UTM.lat_lon_to_utm(location.getLatitude(), location.getLongitude(), this.getContext()));
}
if (mainActivity.prefUtc)
df.setTimeZone(TimeZone.getTimeZone("UTC"));
else
df.setTimeZone(TimeZone.getDefault());
gpsTime.setText(df.format(new Date(location.getTime())));
if (location.hasAltitude()) {
Float getAltitude = (float) 0.0;
if(mainActivity.prefUnitType) {
getAltitude = (float)(location.getAltitude());
} else {
getAltitude = (float)(location.getAltitude() * (float) 3.28084);
}
gpsAlt.setText(String.format("%.0f", getAltitude));
gpsAltUnit.setText(getString(((mainActivity.prefUnitType) ? R.string.unit_meter : R.string.unit_feet)));
orDeclination.setText(String.format("%.0f%s", new GeomagneticField(
(float) location.getLatitude(),
(float) location.getLongitude(),
(float) (getAltitude),
location.getTime()
).getDeclination(), getString(R.string.unit_degree)));
} else {
gpsAlt.setText(getString(R.string.value_none));
gpsAltUnit.setText("");
orDeclination.setText(getString(R.string.value_none));
}
if (location.hasBearing()) {
gpsBearing.setText(String.format("%.0f%s", location.getBearing(), getString(R.string.unit_degree)));
gpsOrientation.setText(MainActivity.formatOrientation(this.getContext(), location.getBearing()));
} else {
gpsBearing.setText(getString(R.string.value_none));
gpsOrientation.setText(getString(R.string.value_none));
}
if (location.hasSpeed()) {
Float getSpeed = (float) 0.0;
if (mainActivity.prefKnots) {
getSpeed = (float)(location.getSpeed() * 1.943844f);
} else if (mainActivity.prefUnitType) {
getSpeed = (float)(location.getSpeed() * 3.6f);
} else {
getSpeed = (float)(location.getSpeed() * 2.23694f);
}
gpsSpeed.setText(String.format("%.0f", getSpeed));
gpsSpeedUnit.setText(getString(((mainActivity.prefKnots) ? R.string.unit_kn : (mainActivity.prefUnitType) ? R.string.unit_km_h : R.string.unit_mph)));
} else {
gpsSpeed.setText(getString(R.string.value_none));
gpsSpeedUnit.setText("");
}
// note: getting number of sats in fix by looking for "satellites"
// in location's extras doesn't seem to work, always returns 0 sats
}
/**
* Called by {@link MainActivity} when a sensor's reading changes.
* Rotates sky plot according to bearing.
*
* If {@code TYPE_ORIENTATION} data is available, preference is given to that value, which
* appeared to be more accurate in tests. Otherwise orientation is obtained from the rotation
* vector of the device, based on {@link TYPE_ACCELEROMETER} and {@code TYPE_MAGNETIC_FIELD}
* sensor data.
*/
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
gravity = event.values.clone();
break;
case Sensor.TYPE_MAGNETIC_FIELD:
geomagnetic = event.values.clone();
break;
case Sensor.TYPE_ORIENTATION:
if (event.values[0] != 0) {
hasOrientation = true;
gpsStatusView.setYaw(event.values[0]);
}
break;
}
if ((gravity != null) && (geomagnetic != null) && !hasOrientation) {
float[] ypr = new float[3];
float[] r = new float[16];
float[] i = new float[16];
SensorManager.getRotationMatrix(r, i, gravity, geomagnetic);
ypr = SensorManager.getOrientation(r, ypr);
gpsStatusView.setYaw((float) Math.toDegrees(ypr[0]));
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/LegendActivity.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.util.Map;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.R;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Gravity;
import android.view.MenuItem;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.ImageView.ScaleType;
public class LegendActivity extends AppCompatActivity {
/*
* Index into style arrays
*/
private static final int STYLE_MARKER = 0;
private LinearLayout legendMapContainer;
protected void addLocationProvider(String title, String styleName) {
Resources res = this.getBaseContext().getResources();
TypedArray style = res.obtainTypedArray(res.getIdentifier(styleName, "array", this.getBaseContext().getPackageName()));
Drawable drawable = style.getDrawable(STYLE_MARKER);
style.recycle();
LinearLayout lpLayout = new LinearLayout(legendMapContainer.getContext());
lpLayout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
lpLayout.setOrientation(LinearLayout.HORIZONTAL);
lpLayout.setWeightSum(22);
lpLayout.setMeasureWithLargestChildEnabled(false);
ImageView lpMarker = new ImageView(legendMapContainer.getContext());
LinearLayout.LayoutParams lpMarkerParams = new LinearLayout.LayoutParams(0, getResources().getDimensionPixelSize(R.dimen.legend_rowheight), 3);
int margin = getResources().getDimensionPixelSize(R.dimen.bitmap_padding);
lpMarkerParams.gravity = Gravity.CENTER;
lpMarker.setLayoutParams(lpMarkerParams);
lpMarker.setPadding(margin, 0, margin, 0);
lpMarker.setImageDrawable(drawable);
lpMarker.setScaleType(ScaleType.CENTER);
lpLayout.addView(lpMarker);
TextView lpDesc = new TextView(legendMapContainer.getContext());
LinearLayout.LayoutParams lpDescParams = new LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 19);
lpDescParams.gravity = Gravity.CENTER_VERTICAL;
lpDesc.setLayoutParams(lpDescParams);
lpDesc.setGravity(Gravity.CENTER_VERTICAL);
lpDesc.setTextAppearance(this, R.style.TextAppearance_AppCompat_Medium);
lpDesc.setText(title);
lpLayout.addView(lpDesc);
legendMapContainer.addView(lpLayout);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_legend);
legendMapContainer = (LinearLayout) findViewById(R.id.legendMapContainer);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this.getBaseContext());
Map allPrefs = sharedPref.getAll();
for (String key: allPrefs.keySet())
if (key.startsWith(Const.KEY_PREF_LOC_PROV_STYLE)) {
String provName = key.substring(Const.KEY_PREF_LOC_PROV_STYLE.length());
String styleName = "";
try {
styleName = sharedPref.getString(key, styleName);
} catch (Exception e) {
Log.w(this.getClass().getSimpleName(), String.format("Cannot retrieve preference %s", key));
}
if (styleName != "")
addLocationProvider(String.format(getString(R.string.title_legend_map_prov, provName)), styleName);
}
addLocationProvider(getString(R.string.title_legend_map_stale), Const.LOCATION_PROVIDER_GRAY);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/MainActivity.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.design.widget.TabLayout;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.ContextThemeWrapper;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import static android.telephony.PhoneStateListener.LISTEN_CELL_INFO;
import static android.telephony.PhoneStateListener.LISTEN_CELL_LOCATION;
import static android.telephony.PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
import static android.telephony.PhoneStateListener.LISTEN_NONE;
import static android.telephony.PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.Surface;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.GpsEventReceiver;
import com.vonglasow.michael.satstat.R;
import com.vonglasow.michael.satstat.data.CellTower;
import com.vonglasow.michael.satstat.data.CellTowerList;
public class MainActivity extends AppCompatActivity implements GpsStatus.Listener, LocationListener, OnSharedPreferenceChangeListener, SensorEventListener {
private static final String TAG = MainActivity.class.getSimpleName();
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a
* {@link android.support.v4.app.FragmentPagerAdapter} derivative, which
* will keep every loaded fragment in memory. If this becomes too memory
* intensive, it may be best to switch to a
* {@link android.support.v4.app.FragmentStatePagerAdapter}.
*/
SectionsPagerAdapter mSectionsPagerAdapter;
/**
* The {@link ViewPager} that will host the section contents.
*/
ViewPager mViewPager;
/**
* The tab view to switch between the fragments of the MainView.
*/
TabLayout mTabLayout;
/**
* Whether the activity is stopped.
*/
boolean isStopped;
/**
* Whether we are running on a wide-screen device
*/
boolean isWideScreen;
/**
* The rate in microseconds at which we would like to receive updates from the sensors.
*
* This is chosen to meet the mission of SatStat, which is to let the human know the values reported
* and evaluate if the sensors are working. At very high rates, the numbers cannot be read, and at
* very slow rates, it is hard to tell if the updates would work at faster rates. We chose 100 ms as
* a reasonable tradeoff.
*/
private static final int iSensorRate = 100000;
GpsSectionFragment gpsSectionFragment = null;
SensorSectionFragment sensorSectionFragment = null;
RadioSectionFragment radioSectionFragment = null;
MapSectionFragment mapSectionFragment = null;
TelephonyManager telephonyManager;
ConnectivityManager connectivityManager;
WifiManager wifiManager;
LocationManager locationManager;
SensorManager sensorManager;
boolean[] permsRequested = new boolean[Const.PERM_REQUEST_MAX + 1];
private Sensor mOrSensor;
private Sensor mAccSensor;
private Sensor mGyroSensor;
private Sensor mMagSensor;
private Sensor mLightSensor;
private Sensor mProximitySensor;
private Sensor mPressureSensor;
private Sensor mHumiditySensor;
private Sensor mTempSensor;
private long mOrLast = 0;
private long mAccLast = 0;
private long mGyroLast = 0;
private long mMagLast = 0;
private long mLightLast = 0;
private long mProximityLast = 0;
private long mPressureLast = 0;
private long mHumidityLast = 0;
private long mTempLast = 0;
/**
* Converts screen rotation to orientation for devices with a naturally tall screen.
*/
private final static Integer OR_FROM_ROT_TALL[] = {
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT,
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE};
/**
* Converts screen rotation to orientation for devices with a naturally wide screen.
*/
private final static Integer OR_FROM_ROT_WIDE[] = {
ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE,
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT,
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT};
SharedPreferences mSharedPreferences;
boolean prefUnitType = true;
boolean prefKnots = false;
int prefCoord = Const.KEY_PREF_COORD_DECIMAL;
boolean prefUtc = false;
boolean prefCid = false;
boolean prefCid2 = false;
int prefWifiSort = 0;
boolean prefMapOffline = false;
String prefMapPath = Const.MAP_PATH_DEFAULT;
/**
* The {@link PhoneStateListener} for getting radio network updates
*/
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public void onCellInfoChanged(List cellInfo) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1)
return;
if (radioSectionFragment != null)
radioSectionFragment.updateCellData(null, null, cellInfo);
}
public void onCellLocationChanged (CellLocation location) {
if (radioSectionFragment != null)
radioSectionFragment.updateCellData(location, null, null);
}
public void onDataConnectionStateChanged (int state, int networkType) {
if (radioSectionFragment != null)
radioSectionFragment.onNetworkTypeChanged(networkType);
}
public void onSignalStrengthsChanged (SignalStrength signalStrength) {
if (radioSectionFragment != null)
radioSectionFragment.updateCellData(null, signalStrength, null);
}
};
/**
* The {@link BroadcastReceiver} for getting radio network updates
*/
private final BroadcastReceiver mWifiScanReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context c, Intent intent) {
if (intent.getAction() == WifiManager.SCAN_RESULTS_AVAILABLE_ACTION) {
if (radioSectionFragment != null) {
radioSectionFragment.scanResults = wifiManager.getScanResults();
radioSectionFragment.refreshWifiResults();
}
} else {
//something has changed about WiFi setup, rescan
wifiManager.startScan();
}
}
};
private Thread.UncaughtExceptionHandler defaultUEH;
/**
* Converts a bearing (in degrees) into a directional name.
*/
public static String formatOrientation(Context context, float bearing) {
return
(bearing < 11.25) ? context.getString(R.string.value_N) :
(bearing < 33.75) ? context.getString(R.string.value_NNE) :
(bearing < 56.25) ? context.getString(R.string.value_NE) :
(bearing < 78.75) ? context.getString(R.string.value_ENE) :
(bearing < 101.25) ? context.getString(R.string.value_E) :
(bearing < 123.75) ? context.getString(R.string.value_ESE) :
(bearing < 146.25) ? context.getString(R.string.value_SE) :
(bearing < 168.75) ? context.getString(R.string.value_SSE) :
(bearing < 191.25) ? context.getString(R.string.value_S) :
(bearing < 213.75) ? context.getString(R.string.value_SSW) :
(bearing < 236.25) ? context.getString(R.string.value_SW) :
(bearing < 258.75) ? context.getString(R.string.value_WSW) :
(bearing < 280.25) ? context.getString(R.string.value_W) :
(bearing < 302.75) ? context.getString(R.string.value_WNW) :
(bearing < 325.25) ? context.getString(R.string.value_NW) :
(bearing < 347.75) ? context.getString(R.string.value_NNW) :
context.getString(R.string.value_N);
}
/**
* Returns the serving cell.
*
* This method iterates through the cell tower lists passed in
* {@code lists} and looks for any entries marked as the serving cell.
*
* @param lists An array of {@link com.vonglasow.michael.satstat.data.CellTowerList}
* instances
* @return The serving cell, if one is found, or {@code null} if none is
* found. If multiple serving cells are found in {@code lists}, no
* assertion is made which cell will be returned, or even that results
* will be consistent between calls.
*/
public static CellTower getServingCell(CellTowerList[] lists) {
for (CellTowerList towers : lists) {
for (CellTower cell : towers.getAll())
if (cell.hasSource() && cell.isServing())
return cell;
}
return null;
}
/**
* Called when a sensor's accuracy has changed. Does nothing.
*/
public void onAccuracyChanged(Sensor sensor, int accuracy) {}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread t, Throwable e) {
Context c = getApplicationContext();
File dumpDir = c.getExternalFilesDir(null);
DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.ROOT);
fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
String fileName = String.format("satstat-%s.log", fmt.format(new Date(System.currentTimeMillis())));
File dumpFile = new File (dumpDir, fileName);
PrintStream s;
try {
InputStream buildInStream = getResources().openRawResource(R.raw.build);
s = new PrintStream(dumpFile);
s.append("SatStat build: ");
int i;
try {
i = buildInStream.read();
while (i != -1) {
s.write(i);
i = buildInStream.read();
}
buildInStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
s.append("\n\n");
e.printStackTrace(s);
s.flush();
s.close();
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(dumpFile);
mediaScanIntent.setData(contentUri);
c.sendBroadcast(mediaScanIntent);
} catch (FileNotFoundException e2) {
e2.printStackTrace();
}
defaultUEH.uncaughtException(t, e);
}
});
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
prefUnitType = mSharedPreferences.getBoolean(Const.KEY_PREF_UNIT_TYPE, prefUnitType);
prefKnots = mSharedPreferences.getBoolean(Const.KEY_PREF_KNOTS, prefKnots);
prefCoord = Integer.valueOf(mSharedPreferences.getString(Const.KEY_PREF_COORD, Integer.toString(prefCoord)));
prefUtc = mSharedPreferences.getBoolean(Const.KEY_PREF_UTC, prefUtc);
prefCid = mSharedPreferences.getBoolean(Const.KEY_PREF_CID, prefCid);
prefCid2 = mSharedPreferences.getBoolean(Const.KEY_PREF_CID2, prefCid2);
prefWifiSort = Integer.valueOf(mSharedPreferences.getString(Const.KEY_PREF_WIFI_SORT, Integer.toString(prefWifiSort)));
prefMapOffline = mSharedPreferences.getBoolean(Const.KEY_PREF_MAP_OFFLINE, prefMapOffline);
prefMapPath = mSharedPreferences.getString(Const.KEY_PREF_MAP_PATH, prefMapPath);
ActionBar actionBar = getSupportActionBar();
setContentView(R.layout.activity_main);
// Find out default screen orientation
Configuration config = getResources().getConfiguration();
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
int rot = wm.getDefaultDisplay().getRotation();
isWideScreen = (config.orientation == Configuration.ORIENTATION_LANDSCAPE &&
(rot == Surface.ROTATION_0 || rot == Surface.ROTATION_180) ||
config.orientation == Configuration.ORIENTATION_PORTRAIT &&
(rot == Surface.ROTATION_90 || rot == Surface.ROTATION_270));
Log.d(TAG, "isWideScreen=" + Boolean.toString(isWideScreen));
// Create the adapter that will return a fragment for each of the
// primary sections of the app.
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
Context ctx = new ContextThemeWrapper(getApplication(), R.style.AppTheme);
mTabLayout = new TabLayout(ctx);
LinearLayout.LayoutParams mTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
mTabLayout.setLayoutParams(mTabLayoutParams);
for (int i = 0; i < mSectionsPagerAdapter.getCount(); i++) {
TabLayout.Tab newTab = mTabLayout.newTab();
newTab.setIcon(mSectionsPagerAdapter.getPageIcon(i));
mTabLayout.addTab(newTab);
}
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(mTabLayout);
mTabLayout.setOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout));
// This is needed by the mapsforge library.
AndroidGraphicFactory.createInstance(this.getApplication());
// Get system services for event delivery
locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
mOrSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
mAccSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mGyroSensor = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
mMagSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
mLightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
mProximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
mPressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
mHumiditySensor = sensorManager.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
mTempSensor = sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
connectivityManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
protected void onDestroy() {
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
super.onDestroy();
}
/**
* Called when the status of the GPS changes. Updates GPS display.
*/
public void onGpsStatusChanged (int event) {
GpsStatus status = locationManager.getGpsStatus(null);
int satsInView = 0;
int satsUsed = 0;
Iterable sats = status.getSatellites();
for (GpsSatellite sat : sats) {
satsInView++;
if (sat.usedInFix()) {
satsUsed++;
}
}
if (gpsSectionFragment != null) {
gpsSectionFragment.onGpsStatusChanged(status, satsInView, satsUsed, sats);
}
if (mapSectionFragment != null) {
mapSectionFragment.onGpsStatusChanged(status, satsInView, satsUsed, sats);
}
}
/**
* Called when a new location is found by a registered location provider.
* Stores the location and updates GPS display and map view.
*/
public void onLocationChanged(Location location) {
// update map view
if (mapSectionFragment != null) {
mapSectionFragment.onLocationChanged(location);
}
// update GPS view
if ((location.getProvider().equals(LocationManager.GPS_PROVIDER)) && (gpsSectionFragment != null)) {
gpsSectionFragment.onLocationChanged(location);
}
}
/**
* Called when a menu item is selected, and triggers the appropriate action.
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_agps) {
Log.i(TAG, "User requested AGPS data update");
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
GpsEventReceiver.refreshAgps(this, false, true);
else
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, Const.PERM_REQUEST_REFRESH_AGPS);
return true;
} else if (itemId == R.id.action_settings) {
startActivity(new Intent(this, SettingsActivity.class));
return true;
} else if (itemId == R.id.action_legend) {
startActivity(new Intent(this, LegendActivity.class));
return true;
} else if (itemId == R.id.action_about) {
startActivity(new Intent(this, AboutActivity.class));
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
/**
* Called when a location provider is disabled. Does nothing.
*/
public void onProviderDisabled(String provider) {}
/**
* Called when a location provider is enabled. Does nothing.
*/
public void onProviderEnabled(String provider) {}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
StringBuilder messageBuilder = new StringBuilder();
for (int i = 0; i < grantResults.length; i++)
if (permissions[i].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(Const.PERM_REQUEST_LOCATION_NOTIFICATION);
if (permsRequested[Const.PERM_REQUEST_PHONE_STATE_LISTENER]) {
registerPhoneStateListener();
permsRequested[Const.PERM_REQUEST_PHONE_STATE_LISTENER] = false;
}
if (permsRequested[Const.PERM_REQUEST_LOCATION_UPDATES]) {
requestLocationUpdates();
permsRequested[Const.PERM_REQUEST_LOCATION_UPDATES] = false;
}
if (permsRequested[Const.PERM_REQUEST_CELL_INFO]) {
if (radioSectionFragment != null)
radioSectionFragment.updateCellData(null, null, null);
permsRequested[Const.PERM_REQUEST_CELL_INFO] = false;
}
if (requestCode == Const.PERM_REQUEST_REFRESH_AGPS)
GpsEventReceiver.refreshAgps(this, false, true);
} else if (requestCode == Const.PERM_REQUEST_REFRESH_AGPS) {
if (messageBuilder.length() > 0)
messageBuilder.append("\n");
messageBuilder.append(getString(R.string.status_perm_refresh_agps));
Log.w(TAG, "Location permission not granted, cannot update AGPS data");
} else {
if (messageBuilder.length() > 0)
messageBuilder.append("\n");
messageBuilder.append(getString(R.string.status_perm_location));
Log.w(TAG, "ACCESS_FINE_LOCATION permission not granted. Location and cell info will not be available.");
} // if grantResults[i]
} else if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
if (permsRequested[Const.PERM_REQUEST_OFFLINE_MAP] && (mapSectionFragment != null)) {
mapSectionFragment.onMapSourceChanged();
permsRequested[Const.PERM_REQUEST_OFFLINE_MAP] = false;
}
} else {
if (messageBuilder.length() > 0)
messageBuilder.append("\n");
messageBuilder.append(getString(R.string.status_perm_offline_map));
Log.w(TAG, "WRITE_EXTERNAL_STORAGE permission not granted. Offline map will not be available.");
}
} // if permissions[i].equals()
// for i
String message = messageBuilder.toString();
if (!message.isEmpty()) {
int length = (message.contains("\n")) ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT;
Toast.makeText(this, message, length).show();
}
}
/**
* Called when a sensor's reading changes. Updates sensor display and rotates sky plot according
* to bearing.
*/
public void onSensorChanged(SensorEvent event) {
//to enforce sensor rate
boolean isRateElapsed = false;
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
isRateElapsed = (event.timestamp / 1000) - mAccLast >= iSensorRate;
// if Z acceleration is greater than X/Y combined, lock rotation, else unlock
if (Math.pow(event.values[2], 2) > Math.pow(event.values[0], 2) + Math.pow(event.values[1], 2)) {
// workaround (SCREEN_ORIENTATION_LOCK is unsupported on API < 18)
if (isWideScreen)
setRequestedOrientation(OR_FROM_ROT_WIDE[this.getWindowManager().getDefaultDisplay().getRotation()]);
else
setRequestedOrientation(OR_FROM_ROT_TALL[this.getWindowManager().getDefaultDisplay().getRotation()]);
} else {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
break;
case Sensor.TYPE_ORIENTATION:
isRateElapsed = (event.timestamp / 1000) - mOrLast >= iSensorRate;
break;
case Sensor.TYPE_GYROSCOPE:
isRateElapsed = (event.timestamp / 1000) - mGyroLast >= iSensorRate;
break;
case Sensor.TYPE_MAGNETIC_FIELD:
isRateElapsed = (event.timestamp / 1000) - mMagLast >= iSensorRate;
break;
case Sensor.TYPE_LIGHT:
isRateElapsed = (event.timestamp / 1000) - mLightLast >= iSensorRate;
break;
case Sensor.TYPE_PROXIMITY:
isRateElapsed = (event.timestamp / 1000) - mProximityLast >= iSensorRate;
break;
case Sensor.TYPE_PRESSURE:
isRateElapsed = (event.timestamp / 1000) - mPressureLast >= iSensorRate;
break;
case Sensor.TYPE_RELATIVE_HUMIDITY:
isRateElapsed = (event.timestamp / 1000) - mHumidityLast >= iSensorRate;
break;
case Sensor.TYPE_AMBIENT_TEMPERATURE:
isRateElapsed = (event.timestamp / 1000) - mTempLast >= iSensorRate;
break;
}
if (!isRateElapsed)
return;
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
mAccLast = event.timestamp / 1000;
break;
case Sensor.TYPE_ORIENTATION:
mOrLast = event.timestamp / 1000;
break;
case Sensor.TYPE_GYROSCOPE:
mGyroLast = event.timestamp / 1000;
break;
case Sensor.TYPE_MAGNETIC_FIELD:
mMagLast = event.timestamp / 1000;
break;
case Sensor.TYPE_LIGHT:
mLightLast = event.timestamp / 1000;
break;
case Sensor.TYPE_PROXIMITY:
mProximityLast = event.timestamp / 1000;
break;
case Sensor.TYPE_PRESSURE:
mPressureLast = event.timestamp / 1000;
break;
case Sensor.TYPE_RELATIVE_HUMIDITY:
mHumidityLast = event.timestamp / 1000;
break;
case Sensor.TYPE_AMBIENT_TEMPERATURE:
mTempLast = event.timestamp / 1000;
break;
}
if (sensorSectionFragment != null) {
sensorSectionFragment.onSensorChanged(event);
}
if (gpsSectionFragment != null) {
gpsSectionFragment.onSensorChanged(event);
}
}
/**
* Called when preferences are changed.
*
* This method processes changed to KEY_PREF_LOC_PROV, the list of selected
* location providers. When called, it will unregister for all location
* updates and re-register for updates from the selected location providers.
* (This includes unregistering and immediately re-registering for those
* providers which remain selected – this is due to the fact that Android
* does not support unregistering from a single location provider.)
*/
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key.equals(Const.KEY_PREF_LOC_PROV)) {
// user selected or deselected location providers, refresh list
registerLocationProviders();
} else if (key.equals(Const.KEY_PREF_UNIT_TYPE)) {
prefUnitType = sharedPreferences.getBoolean(Const.KEY_PREF_UNIT_TYPE, prefUnitType);
} else if (key.equals(Const.KEY_PREF_KNOTS)) {
prefKnots = sharedPreferences.getBoolean(Const.KEY_PREF_KNOTS, prefKnots);
} else if (key.equals(Const.KEY_PREF_COORD)) {
prefCoord = Integer.valueOf(mSharedPreferences.getString(Const.KEY_PREF_COORD, Integer.toString(prefCoord)));
} else if (key.equals(Const.KEY_PREF_UTC)) {
prefUtc = sharedPreferences.getBoolean(Const.KEY_PREF_UTC, prefUtc);
} else if (key.equals(Const.KEY_PREF_CID)) {
prefCid = sharedPreferences.getBoolean(Const.KEY_PREF_CID, prefCid);
} else if (key.equals(Const.KEY_PREF_CID2)) {
prefCid2 = sharedPreferences.getBoolean(Const.KEY_PREF_CID2, prefCid2);
} else if (key.equals(Const.KEY_PREF_WIFI_SORT)) {
prefWifiSort = Integer.valueOf(mSharedPreferences.getString(Const.KEY_PREF_WIFI_SORT, Integer.toString(prefWifiSort)));
} else if (key.equals(Const.KEY_PREF_MAP_OFFLINE)) {
prefMapOffline = sharedPreferences.getBoolean(Const.KEY_PREF_MAP_OFFLINE, prefMapOffline);
if (mapSectionFragment != null)
mapSectionFragment.onMapSourceChanged();
if (prefMapOffline && (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED))
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Const.PERM_REQUEST_OFFLINE_MAP);
} else if (key.equals(Const.KEY_PREF_MAP_PATH)) {
prefMapPath = sharedPreferences.getString(Const.KEY_PREF_MAP_PATH, prefMapPath);
if (mapSectionFragment != null)
mapSectionFragment.onMapSourceChanged();
} else if (key.equals(Const.KEY_PREF_MAP_PURGE)) {
if (sharedPreferences.getBoolean(Const.KEY_PREF_MAP_PURGE, false) && (mapSectionFragment != null))
mapSectionFragment.onMapSourceChanged();
}
}
@Override
protected void onStart() {
super.onStart();
isStopped = false;
registerLocationProviders();
sensorManager.registerListener(this, mOrSensor, iSensorRate);
sensorManager.registerListener(this, mAccSensor, iSensorRate);
sensorManager.registerListener(this, mGyroSensor, iSensorRate);
sensorManager.registerListener(this, mMagSensor, iSensorRate);
sensorManager.registerListener(this, mLightSensor, iSensorRate);
sensorManager.registerListener(this, mProximitySensor, iSensorRate);
sensorManager.registerListener(this, mPressureSensor, iSensorRate);
sensorManager.registerListener(this, mHumiditySensor, iSensorRate);
sensorManager.registerListener(this, mTempSensor, iSensorRate);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
registerPhoneStateListener();
else
permsRequested[Const.PERM_REQUEST_PHONE_STATE_LISTENER] = true;
// register for certain WiFi events indicating that new networks may be in range
// An access point scan has completed, and results are available.
registerReceiver(mWifiScanReceiver, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
// The state of Wi-Fi connectivity has changed.
registerReceiver(mWifiScanReceiver, new IntentFilter(WifiManager.NETWORK_STATE_CHANGED_ACTION));
// The RSSI (signal strength) has changed.
registerReceiver(mWifiScanReceiver, new IntentFilter(WifiManager.RSSI_CHANGED_ACTION));
// A connection to the supplicant has been established or the connection to the supplicant has been lost.
registerReceiver(mWifiScanReceiver, new IntentFilter(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION));
permsRequested[Const.PERM_REQUEST_OFFLINE_MAP] = prefMapOffline;
/*
* Refresh map layers when offline map is selected and we have storage permission
* (it might have been granted while we were gone, in which case we wouldn't have the layer)
*/
if (prefMapOffline
&& (mapSectionFragment != null)
&& (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED))
mapSectionFragment.onMapSourceChanged();
requestPermissions();
}
/**
* Called when a location provider's status changes. Does nothing.
*/
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
protected void onStop() {
isStopped = true;
locationManager.removeUpdates(this);
locationManager.removeGpsStatusListener(this);
sensorManager.unregisterListener(this);
telephonyManager.listen(mPhoneStateListener, LISTEN_NONE);
try {
unregisterReceiver(mWifiScanReceiver);
} catch (IllegalArgumentException e) {
// sometimes the receiver isn't registered, make sure we don't crash
Log.d(TAG, "WifiScanReceiver was never registered, caught exception");
}
// we'll just skip that so locations will get invalidated in any case
//providerInvalidationHandler.removeCallbacksAndMessages(null);
super.onStop();
}
/**
* Registers for updates with selected location providers.
*/
protected void registerLocationProviders() {
Set providers = new HashSet(mSharedPreferences.getStringSet(Const.KEY_PREF_LOC_PROV, new HashSet(Arrays.asList(new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER}))));
locationManager.removeUpdates(this);
if (mapSectionFragment != null)
mapSectionFragment.onLocationProvidersChanged(providers);
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
requestLocationUpdates();
else
permsRequested[Const.PERM_REQUEST_LOCATION_UPDATES] = true;
}
/**
* Registers the {@link PhoneStateListener} for all necessary events
*/
private void registerPhoneStateListener() {
telephonyManager.listen(mPhoneStateListener, (LISTEN_CELL_INFO | LISTEN_CELL_LOCATION | LISTEN_DATA_CONNECTION_STATE | LISTEN_SIGNAL_STRENGTHS));
}
/**
* Requests location updates from the selected location providers.
*
* This method is intended to be called by {@link #registerLocationProviders(Context)} or by
* {@link #onRequestPermissionsResult(int, String[], int[])}, depending on whether permissions need to be
* requested.
*/
private void requestLocationUpdates() {
Set providers = new HashSet(mSharedPreferences.getStringSet(Const.KEY_PREF_LOC_PROV, new HashSet(Arrays.asList(new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER}))));
List allProviders = locationManager.getAllProviders();
if (!isStopped) {
for (String pr : providers) {
if (allProviders.indexOf(pr) >= 0) {
try {
locationManager.requestLocationUpdates(pr, 0, 0, this);
Log.d(TAG, "Registered with provider: " + pr);
} catch (SecurityException e) {
Log.w(TAG, "Permission not granted for " + pr + " location provider. Data display will not be available for this provider.");
}
} else {
Log.w(TAG, "No " + pr + " location provider found. Data display will not be available for this provider.");
}
}
}
try {
// if GPS is not selected, request location updates but don't store location
if ((!providers.contains(LocationManager.GPS_PROVIDER)) && (!isStopped) && (allProviders.indexOf(LocationManager.GPS_PROVIDER) >= 0))
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this);
locationManager.addGpsStatusListener(this);
} catch (SecurityException e) {
Log.w(TAG, "Permission not granted for " + LocationManager.GPS_PROVIDER + " location provider. Data display will not be available for this provider.");
}
}
private void requestPermissions() {
ArrayList perms = new ArrayList();
if (permsRequested[Const.PERM_REQUEST_PHONE_STATE_LISTENER]
|| permsRequested[Const.PERM_REQUEST_LOCATION_UPDATES]
|| permsRequested[Const.PERM_REQUEST_CELL_INFO]) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED)
perms.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (permsRequested[Const.PERM_REQUEST_OFFLINE_MAP]) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
perms.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
if (perms.size() > 0)
ActivityCompat.requestPermissions(this, perms.toArray(new String[]{}), Const.PERM_REQUEST_STARTUP);
}
/**
* A {@link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a DummySectionFragment (defined as a static inner class
// below) with the page number as its lone argument.
Fragment fragment;
switch (position) {
case 0:
fragment = new GpsSectionFragment();
return fragment;
case 1:
fragment = new SensorSectionFragment();
return fragment;
case 2:
fragment = new RadioSectionFragment();
return fragment;
case 3:
fragment = new MapSectionFragment();
return fragment;
}
return null;
}
@Override
public int getCount() {
// Show 4 total pages.
return 4;
}
public Drawable getPageIcon(int position) {
switch (position) {
case 0:
return getResources().getDrawable(R.drawable.ic_action_gps);
case 1:
return getResources().getDrawable(R.drawable.ic_action_sensor);
case 2:
return getResources().getDrawable(R.drawable.ic_action_radio);
case 3:
return getResources().getDrawable(R.drawable.ic_action_map);
}
return null;
}
@Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
case 3:
return getString(R.string.title_section4).toUpperCase(l);
}
return null;
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/MapDownloadActivity.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.util.List;
import pl.polidea.treeview.DownloadTreeStateManager;
import pl.polidea.treeview.TreeBuilder;
import pl.polidea.treeview.TreeViewList;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.R;
import com.vonglasow.michael.satstat.utils.DownloadTreeViewAdapter;
import com.vonglasow.michael.satstat.utils.RemoteDirListTask;
import com.vonglasow.michael.satstat.utils.RemoteDirListListener;
import com.vonglasow.michael.satstat.utils.RemoteFile;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
/**
* An activity which displays a list of maps available on the download server and lets the user
* select maps to download.
*/
public class MapDownloadActivity extends AppCompatActivity implements RemoteDirListListener {
private static final String TAG = MapDownloadActivity.class.getSimpleName();
// FTP is also available but we don't support it yet
//public static final String MAP_DOWNLOAD_BASE_URL = "ftp://ftp-stud.hs-esslingen.de/pub/Mirrors/download.mapsforge.org/maps/";
public static final String MAP_DOWNLOAD_BASE_URL = "https://ftp-stud.hs-esslingen.de/pub/Mirrors/download.mapsforge.org/maps/v4/";
// TODO there's also the Mapsforge download server as a fallback
private static final String STATE_KEY_TREE_MANAGER = "treeManager";
private static final String STATE_KEY_DOWNLOADS = "downloads";
RemoteDirListTask dirListTask = null;
ProgressBar downloadProgress;
LinearLayout downloadErrorLayout;
Button downloadRetry;
private TreeViewList treeView;
private DownloadTreeStateManager manager = null;
private TreeBuilder builder = null;
private DownloadTreeViewAdapter treeViewAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
Bundle state = savedInstanceState;
if (state == null)
state = this.getIntent().getBundleExtra(Const.KEY_SAVED_INSTANCE_STATE);
super.onCreate(state);
if (state != null) {
manager = (DownloadTreeStateManager) state.getSerializable(STATE_KEY_TREE_MANAGER);
}
if (manager == null)
manager = new DownloadTreeStateManager();
builder = new TreeBuilder(manager);
setContentView(R.layout.activity_map_download);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
downloadProgress = (ProgressBar) findViewById(R.id.downloadProgress);
downloadErrorLayout = (LinearLayout) findViewById(R.id.downloadErrorLayout);
downloadRetry = (Button) findViewById(R.id.downloadRetry);
treeView = (TreeViewList) findViewById(R.id.downloadList);
/*
* FIXME: Android wants the number of distinct layouts, which here is the same as the number of
* levels and in theory unlimited. Using more levels than specified here will cause exceptions which
* are beyond our control (only system functions in the call stack) and semi-random (creating more
* levels than specified will work initially but the code will barf sometime later, e.g. on scroll).
*
* The maximum number of levels is currently 4 (multilingual/continent/country/region.map),
* therefore 5 is safe even if another one level is added. However, if the layout on the server ever
* changes and goes beyond that, we'll get semi-random crashes.
*/
treeViewAdapter = new DownloadTreeViewAdapter(this, manager, 5);
treeView.setAdapter(treeViewAdapter);
treeView.setCollapsible(true);
treeView.setCollapsedDrawable(getResources().getDrawable(R.drawable.ic_expand_more));
treeView.setExpandedDrawable(getResources().getDrawable(R.drawable.ic_expand_less));
treeView.setIndentWidth(24);
downloadErrorLayout.setVisibility(View.GONE);
OnClickListener clis = new OnClickListener () {
@Override
public void onClick(View v) {
if (v == downloadRetry) {
treeView.setVisibility(View.GONE);
downloadErrorLayout.setVisibility(View.GONE);
downloadProgress.setVisibility(View.VISIBLE);
// get data from server
dirListTask = new RemoteDirListTask(MapDownloadActivity.this, null);
dirListTask.execute(MAP_DOWNLOAD_BASE_URL);
}
}
};
downloadRetry.setOnClickListener(clis);
List topItems = manager.getChildren(null);
if ((topItems == null) || (topItems.size() == 0)) {
treeView.setVisibility(View.GONE);
downloadProgress.setVisibility(View.VISIBLE);
// get data from server
dirListTask = new RemoteDirListTask(this, null);
dirListTask.execute(MAP_DOWNLOAD_BASE_URL);
}
treeViewAdapter.registerIntentReceiver();
}
@Override
protected void onDestroy() {
if ((dirListTask != null) && (!dirListTask.isCancelled()))
dirListTask.cancel(true);
treeViewAdapter.releaseIntentReceiver();
super.onDestroy();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onRemoteDirListReady(RemoteDirListTask task, RemoteFile[] rfiles) {
downloadProgress.setVisibility(View.GONE);
builder.clear();
if (rfiles != null) {
treeView.setVisibility(View.VISIBLE);
downloadErrorLayout.setVisibility(View.GONE);
for (RemoteFile rf : rfiles)
builder.sequentiallyAddNextNode(rf, 0);
} else {
treeView.setVisibility(View.GONE);
downloadErrorLayout.setVisibility(View.VISIBLE);
}
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onSaveInstanceState(final Bundle outState) {
outState.putSerializable(STATE_KEY_TREE_MANAGER, manager);
super.onSaveInstanceState(outState);
}
@Override
protected void onStop() {
if (treeViewAdapter != null) {
Bundle outState = new Bundle();
this.onSaveInstanceState(outState);
treeViewAdapter.storeInstanceState(outState);
}
super.onStop();
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/MapSectionFragment.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapsforge.core.graphics.Bitmap;
import org.mapsforge.core.graphics.Paint;
import org.mapsforge.core.graphics.Style;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.core.model.Dimension;
import org.mapsforge.core.model.LatLong;
import org.mapsforge.core.model.Point;
import org.mapsforge.core.util.LatLongUtils;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.android.input.MapZoomControls.Orientation;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.android.view.MapView;
import org.mapsforge.map.datastore.MultiMapDataStore;
import org.mapsforge.map.datastore.MultiMapDataStore.DataPolicy;
import org.mapsforge.map.layer.Layer;
import org.mapsforge.map.layer.LayerManager;
import org.mapsforge.map.layer.Layers;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.download.TileDownloadLayer;
import org.mapsforge.map.layer.download.tilesource.OnlineTileSource;
import org.mapsforge.map.layer.overlay.Circle;
import org.mapsforge.map.layer.overlay.Marker;
import org.mapsforge.map.layer.renderer.TileRendererLayer;
import org.mapsforge.map.reader.MapFile;
import org.mapsforge.map.reader.header.MapFileException;
import org.mapsforge.map.rendertheme.InternalRenderTheme;
import org.mapsforge.map.util.MapViewProjection;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.R;
import android.Manifest;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.TextView;
/**
* The fragment which displays the map view.
*/
public class MapSectionFragment extends Fragment {
public static final String TAG = "MapSectionFragment";
/**
* The fragment argument representing the section number for this
* fragment.
*/
public static final String ARG_SECTION_NUMBER = "section_number";
private static final String KEY_LOCATION_STALE = "isStale";
private static final int PROVIDER_EXPIRATION_DELAY = 6000; // the time after which a location is considered stale
private MainActivity mainActivity = null;
OnlineTileSource onlineTileSource;
private MapView mapMap;
private TileDownloadLayer mapDownloadLayer = null;
private TileRendererLayer mapRendererLayer = null;
private TileCache mapDownloadTileCache = null;
private TileCache mapRendererTileCache = null;
private ImageButton mapReattach;
private TextView mapAttribution;
private boolean isMapViewAttached = true;
private HashMap mapCircles;
private HashMap mapMarkers;
/**
* Cached map of locations reported by the providers.
*
* The keys correspond to the provider names as defined by LocationManager.
* The entries are {@link Location} instances. For valid and recent
* locations these are copies of the locations supplied by
* {@link LocationManager}. Invalid locations, intended as placeholders,
* have an empty provider string and should not be processed. Stale
* locations have isStale entry in their extras set to true. They can be
* processed but may require special handling.
*/
private HashMap providerLocations;
private HashMap providerStyles;
private HashMap providerAppliedStyles;
private List mAvailableProviderStyles;
private Handler providerInvalidationHandler = null;
private HashMap providerInvalidators;
public MapSectionFragment() {
}
/**
* Applies a style to the map overlays associated with a given location provider.
*
* This method changes the style (effectively, the color) of the circle and
* marker overlays. Its main purpose is to switch the color of the overlays
* between gray and the provider color.
*
* @param context The context of the caller
* @param provider The name of the location provider, as returned by
* {@link LocationProvider.getName()}.
* @param styleName The name of the style to apply. If it is null, the
* default style for the provider as returned by
* assignLocationProviderStyle() is applied.
*/
protected void applyLocationProviderStyle(Context context, String provider, String styleName) {
String sn = (styleName != null)?styleName:assignLocationProviderStyle(provider);
Boolean isStyleChanged = !sn.equals(providerAppliedStyles.get(provider));
Boolean needsRedraw = false;
Resources res = context.getResources();
TypedArray style = res.obtainTypedArray(res.getIdentifier(sn, "array", context.getPackageName()));
// Circle layer
Circle circle = mapCircles.get(provider);
if (circle != null) {
circle.getPaintFill().setColor(style.getColor(Const.STYLE_FILL, R.color.circle_gray_fill));
circle.getPaintStroke().setColor(style.getColor(Const.STYLE_STROKE, R.color.circle_gray_stroke));
needsRedraw = isStyleChanged && circle.isVisible();
}
//Marker layer
Marker marker = mapMarkers.get(provider);
if (marker != null) {
Drawable drawable = style.getDrawable(Const.STYLE_MARKER);
Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(drawable);
marker.setBitmap(bitmap);
needsRedraw = needsRedraw || (isStyleChanged && marker.isVisible());
}
if (needsRedraw) {
LayerManager manager = mapMap.getLayerManager();
if (manager != null)
manager.redrawLayers();
}
providerAppliedStyles.put(provider, sn);
style.recycle();
}
/**
* Returns the map overlay style to use for a given location provider.
*
* This method first checks if a style has already been assigned to the
* location provider. In that case the already assigned style is returned.
* Otherwise a new style is assigned and the assignment is stored
* internally and written to SharedPreferences.
* @param provider
* @return The style to use for non-stale locations
*/
protected String assignLocationProviderStyle(String provider) {
String styleName = providerStyles.get(provider);
if (styleName == null) {
/*
* Not sure if this ever happens but I can't rule it out. Scenarios I can think of:
* - A custom location provider which identifies itself as "passive"
* - A combination of the following:
* - Passive location provider is selected
* - A new provider is added while we're running (so it's not in our list)
* - Another app starts using the new provider
* - The passive location provider forwards us an update from the new provider
*/
if (mAvailableProviderStyles.isEmpty())
mAvailableProviderStyles.addAll(Arrays.asList(Const.LOCATION_PROVIDER_STYLES));
styleName = mainActivity.mSharedPreferences.getString(Const.KEY_PREF_LOC_PROV_STYLE + provider, mAvailableProviderStyles.get(0));
providerStyles.put(provider, styleName);
SharedPreferences.Editor spEditor = mainActivity.mSharedPreferences.edit();
spEditor.putString(Const.KEY_PREF_LOC_PROV_STYLE + provider, styleName);
spEditor.commit();
}
return styleName;
}
/**
* Creates layers and associated tile caches for the map view.
*
* @param createOverlays Whether to create overlays (circle and markers) or just tile layers
*/
private void createLayers(boolean createOverlays) {
LayerManager layerManager = mapMap.getLayerManager();
Layers layers = layerManager.getLayers();
if (mainActivity.prefMapOffline && (ContextCompat.checkSelfPermission(mainActivity, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) {
/*
* If offline maps are enabled AND we have storage permission, use offline map tiles.
* Skip this step if we don't have permission, else we would pollute the cache with blank tiles.
*/
if (mapRendererTileCache == null)
mapRendererTileCache = AndroidUtil.createExternalStorageTileCache(this.getContext(),
Const.TILE_CACHE_INTERNAL_RENDER_THEME,
Math.round(AndroidUtil.getMinimumCacheSize(this.getContext(),
mapMap.getModel().displayModel.getTileSize(),
mapMap.getModel().frameBufferModel.getOverdrawFactor(),
1f)),
mapMap.getModel().displayModel.getTileSize(),
true);
/*
* If the offline map path changes, we need to purge the cache. This ensures we don't serve stale
* tiles generated from the old map set.
*
* We accomplish this by comparing the current map path to the map path for which we last
* instantiated a cache. If they differ, we flush the cache and store the new map path.
*/
String cachedPath = mainActivity.mSharedPreferences.getString(Const.KEY_PREF_MAP_CACHED_PATH, "");
if (!cachedPath.equals(mainActivity.prefMapPath)) {
mapRendererTileCache.purge();
SharedPreferences.Editor spEditor = mainActivity.mSharedPreferences.edit();
spEditor.putString(Const.KEY_PREF_MAP_CACHED_PATH, mainActivity.prefMapPath);
spEditor.commit();
}
MultiMapDataStore mapDataStore = new MultiMapDataStore(DataPolicy.DEDUPLICATE);
File mapDir = new File(mainActivity.prefMapPath);
Log.i(TAG, String.format("Looking for maps in: %s", mapDir.getName()));
if (mapDir.exists() && mapDir.canRead() && mapDir.isDirectory())
for (File file : mapDir.listFiles())
if (file.isFile())
try {
MapFile mapFile = new MapFile(file);
mapDataStore.addMapDataStore(mapFile, false, false);
Log.i(TAG, String.format("Added map file: %s", file.getName()));
} catch (MapFileException e) {
// not a map file, skip
Log.w(TAG, String.format("Could not add map file: %s", file.getName()));
}
mapRendererLayer = new TileRendererLayer(mapRendererTileCache, mapDataStore,
mapMap.getModel().mapViewPosition, false, true, false, AndroidGraphicFactory.INSTANCE);
mapRendererLayer.setXmlRenderTheme(InternalRenderTheme.OSMARENDER);
//mapRendererLayer.setTextScale(1.5f); // FIXME
layers.add(0, mapRendererLayer);
mapAttribution.setText(R.string.osm_attribution);
} else if (!mainActivity.prefMapOffline) {
// use online map tiles
if (mapDownloadTileCache == null)
mapDownloadTileCache = AndroidUtil.createExternalStorageTileCache(this.getContext(),
Const.TILE_CACHE_OSM,
Math.round(AndroidUtil.getMinimumCacheSize(this.getContext(),
mapMap.getModel().displayModel.getTileSize(),
mapMap.getModel().frameBufferModel.getOverdrawFactor(),
1f)),
mapMap.getModel().displayModel.getTileSize(),
true);
mapDownloadLayer = new TileDownloadLayer(mapDownloadTileCache,
mapMap.getModel().mapViewPosition, onlineTileSource,
AndroidGraphicFactory.INSTANCE);
layers.add(0, mapDownloadLayer);
/*
* Since tiles are now sourced from OSM (following Mapquest's decision to discontinue their free
* tile service), attribution is the same for online and offline. This may change if we switch
* to a different tile source (or allow multiple ones) - therefore, attribution should still
* depend on the map source.
*/
mapAttribution.setText(R.string.osm_attribution);
} else
mapAttribution.setText("");
//parse list of location providers
if (createOverlays)
onLocationProvidersChanged(
mainActivity.mSharedPreferences.getStringSet(
Const.KEY_PREF_LOC_PROV,
new HashSet(Arrays.asList(
new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER}
))));
// mark cache as purged
SharedPreferences.Editor spEditor = mainActivity.mSharedPreferences.edit();
spEditor.putBoolean(Const.KEY_PREF_MAP_PURGE, false);
spEditor.commit();
}
/**
* Destroys layers and associated tile caches for the map view.
*
* @param destroyOverlays Whether to destroy overlays (markers and circles) or just the tile layers
*/
private void destroyLayers(boolean destroyOverlays) {
Layers layers = null;
if (mapMap != null)
layers = mapMap.getLayerManager().getLayers();
if (mapDownloadLayer != null) {
if (layers != null)
layers.remove(mapDownloadLayer);
mapDownloadLayer.onDestroy();
mapDownloadLayer = null;
}
if (mapRendererLayer != null) {
if (layers != null)
layers.remove(mapRendererLayer);
mapRendererLayer.onDestroy();
mapRendererLayer = null;
}
if (mapDownloadTileCache != null) {
mapDownloadTileCache.destroy();
mapDownloadTileCache = null;
}
if (mapRendererTileCache != null) {
mapRendererTileCache.destroy();
mapRendererTileCache = null;
}
if (destroyOverlays && (layers != null))
for (Layer layer : layers) {
layer.onDestroy();
layers.remove(layer);
}
}
/**
* Determines if a location is stale.
*
* A location is considered stale if its Extras have an isStale key set to
* True. A location without this key is not considered stale.
*
* @param location
* @return True if stale, False otherwise
*/
public static boolean isLocationStale(Location location) {
Bundle extras = location.getExtras();
if (extras == null)
return false;
return extras.getBoolean(KEY_LOCATION_STALE);
}
public static void markLocationAsStale(Location location) {
if (location.getExtras() == null)
location.setExtras(new Bundle());
location.getExtras().putBoolean(KEY_LOCATION_STALE, true);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mainActivity = (MainActivity) this.getContext();
View rootView = inflater.inflate(R.layout.fragment_main_map, container, false);
float density = this.getContext().getResources().getDisplayMetrics().density;
String versionName;
try {
versionName = mainActivity.getPackageManager().getPackageInfo(mainActivity.getPackageName(), 0).versionName;
} catch (NameNotFoundException e) {
versionName = "unknown";
}
mapReattach = (ImageButton) rootView.findViewById(R.id.mapReattach);
mapAttribution = (TextView) rootView.findViewById(R.id.mapAttribution);
mapReattach.setVisibility(View.GONE);
isMapViewAttached = true;
OnClickListener clis = new OnClickListener () {
@Override
public void onClick(View v) {
if (v == mapReattach) {
isMapViewAttached = true;
mapReattach.setVisibility(View.GONE);
updateMap();
}
}
};
mapReattach.setOnClickListener(clis);
// Initialize controls
mapMap = new MapView(rootView.getContext());
((FrameLayout) rootView).addView(mapMap, 0);
mapMap.setClickable(true);
mapMap.getMapScaleBar().setVisible(true);
mapMap.getMapScaleBar().setMarginVertical((int)(density * 16));
mapMap.setBuiltInZoomControls(true);
mapMap.getMapZoomControls().setZoomLevelMin((byte) 10);
mapMap.getMapZoomControls().setZoomLevelMax((byte) 20);
mapMap.getMapZoomControls().setZoomControlsOrientation(Orientation.VERTICAL_IN_OUT);
mapMap.getMapZoomControls().setZoomInResource(R.drawable.zoom_control_in);
mapMap.getMapZoomControls().setZoomOutResource(R.drawable.zoom_control_out);
mapMap.getMapZoomControls().setMarginHorizontal((int)(density * 8));
mapMap.getMapZoomControls().setMarginVertical((int)(density * 16));
providerLocations = new HashMap();
mAvailableProviderStyles = new ArrayList(Arrays.asList(Const.LOCATION_PROVIDER_STYLES));
providerStyles = new HashMap();
providerAppliedStyles = new HashMap();
providerInvalidationHandler = new Handler();
providerInvalidators = new HashMap();
onlineTileSource = new OnlineTileSource(Const.TILE_SERVER_OSM, 80);
onlineTileSource.setUserAgent(String.format("%s/%s (%s)", "SatStat", versionName, System.getProperty("http.agent")));
onlineTileSource.setName(Const.TILE_CACHE_OSM)
.setAlpha(false)
.setBaseUrl(Const.TILE_URL_OSM)
.setExtension(Const.TILE_EXTENSION_OSM)
.setParallelRequestsLimit(8)
.setProtocol("http")
.setTileSize(256)
.setZoomLevelMax((byte) 18)
.setZoomLevelMin((byte) 0);
GestureDetector gd = new GestureDetector(rootView.getContext(),
new GestureDetector.SimpleOnGestureListener() {
public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mapReattach.setVisibility(View.VISIBLE);
isMapViewAttached = false;
return false;
}
}
);
mapMap.setGestureDetector(gd);
mainActivity.mapSectionFragment = this;
float lat = mainActivity.mSharedPreferences.getFloat(Const.KEY_PREF_MAP_LAT, 360.0f);
float lon = mainActivity.mSharedPreferences.getFloat(Const.KEY_PREF_MAP_LON, 360.0f);
if ((lat < 360.0f) && (lon < 360.0f)) {
mapMap.getModel().mapViewPosition.setCenter(new LatLong(lat, lon));
}
int zoom = mainActivity.mSharedPreferences.getInt(Const.KEY_PREF_MAP_ZOOM, 16);
mapMap.getModel().mapViewPosition.setZoomLevel((byte) zoom);
createLayers(true);
return rootView;
}
@Override
public void onDestroyView() {
destroyLayers(true);
if (mainActivity.mapSectionFragment == this)
mainActivity.mapSectionFragment = null;
if (mapMap != null)
mapMap.destroyAll();
AndroidGraphicFactory.clearResourceMemoryCache();
super.onDestroyView();
}
/**
* Called by {@link MainActivity} when the status of the GPS changes. Updates GPS display.
*/
public void onGpsStatusChanged(GpsStatus status, int satsInView, int satsUsed, Iterable sats) {
if (satsUsed == 0) {
Location location = providerLocations.get(LocationManager.GPS_PROVIDER);
if (location != null)
markLocationAsStale(location);
applyLocationProviderStyle(this.getContext(), LocationManager.GPS_PROVIDER, Const.LOCATION_PROVIDER_GRAY);
}
}
/**
* Called when a new location is found by a registered location provider.
* Stores the location and updates GPS display and map view.
*/
public void onLocationChanged(Location location) {
// some providers may report NaN for latitude and longitude:
// if that happens, do not process this location and mark any previous
// location from that provider as stale
if (Double.isNaN(location.getLatitude()) || Double.isNaN(location.getLongitude())) {
markLocationAsStale(providerLocations.get(location.getProvider()));
applyLocationProviderStyle(this.getContext(), location.getProvider(), Const.LOCATION_PROVIDER_GRAY);
return;
}
if (providerLocations.containsKey(location.getProvider()))
providerLocations.put(location.getProvider(), new Location(location));
LatLong latLong = new LatLong(location.getLatitude(), location.getLongitude());
Circle circle = mapCircles.get(location.getProvider());
Marker marker = mapMarkers.get(location.getProvider());
if (circle != null) {
circle.setLatLong(latLong);
if (location.hasAccuracy()) {
circle.setVisible(true);
circle.setRadius(location.getAccuracy());
} else {
Log.d("MainActivity", "Location from " + location.getProvider() + " has no accuracy");
circle.setVisible(false);
}
}
if (marker != null) {
marker.setLatLong(latLong);
marker.setVisible(true);
}
applyLocationProviderStyle(this.getContext(), location.getProvider(), null);
Runnable invalidator = providerInvalidators.get(location.getProvider());
if (invalidator != null) {
providerInvalidationHandler.removeCallbacks(invalidator);
providerInvalidationHandler.postDelayed(invalidator, PROVIDER_EXPIRATION_DELAY);
}
// redraw, move locations into view and zoom out as needed
if ((circle != null) || (marker != null) || (invalidator != null))
updateMap();
}
/**
* Updates internal data structures when the user's selection of location providers has changed.
* @param providers The new set of location providers
*/
public void onLocationProvidersChanged(Set providers) {
Context context = this.getContext();
List allProviders = mainActivity.locationManager.getAllProviders();
ArrayList removedProviders = new ArrayList();
for (String pr : providerLocations.keySet())
if (!providers.contains(pr))
removedProviders.add(pr);
// remove cached locations and invalidators for providers which are no longer selected
for (String pr: removedProviders) {
providerLocations.remove(pr);
providerInvalidators.remove(pr);
}
// ensure there is a cached location for each chosen provider (can be null)
for (String pr : providers) {
if ((allProviders.indexOf(pr) >= 0) && !providerLocations.containsKey(pr)) {
Location location = new Location("");
providerLocations.put(pr, location);
}
}
// add overlays
updateLocationProviderStyles();
mapCircles = new HashMap();
mapMarkers = new HashMap();
Log.d(TAG, "Provider location cache: " + providerLocations.keySet().toString());
Layers layers = mapMap.getLayerManager().getLayers();
// remove all layers other than tile render layer from map
for (Layer layer : layers)
if (!(layer instanceof TileRendererLayer) && !(layer instanceof TileDownloadLayer)) {
layer.onDestroy();
layers.remove(layer);
}
for (String pr : providers) {
// no invalidator for GPS, which is invalidated through GPS status
if ((!pr.equals(LocationManager.GPS_PROVIDER)) && (providerInvalidators.get(pr)) == null) {
final String provider = pr;
final Context ctx = context;
providerInvalidators.put(pr, new Runnable() {
private String mProvider = provider;
@Override
public void run() {
Location location = providerLocations.get(mProvider);
if (location != null)
markLocationAsStale(location);
applyLocationProviderStyle(ctx, mProvider, Const.LOCATION_PROVIDER_GRAY);
}
});
}
String styleName = assignLocationProviderStyle(pr);
LatLong latLong;
float acc;
boolean visible;
if ((providerLocations.get(pr) != null) && (providerLocations.get(pr).getProvider() != "")) {
latLong = new LatLong(providerLocations.get(pr).getLatitude(),
providerLocations.get(pr).getLongitude());
if (providerLocations.get(pr).hasAccuracy())
acc = providerLocations.get(pr).getAccuracy();
else
acc = 0;
visible = true;
if (isLocationStale(providerLocations.get(pr)))
styleName = Const.LOCATION_PROVIDER_GRAY;
Log.d("MainActivity", pr + " has " + latLong.toString());
} else {
latLong = new LatLong(0, 0);
acc = 0;
visible = false;
Log.d("MainActivity", pr + " has no location, hiding");
}
// Circle layer
Resources res = context.getResources();
TypedArray style = res.obtainTypedArray(res.getIdentifier(styleName, "array", context.getPackageName()));
Paint fill = AndroidGraphicFactory.INSTANCE.createPaint();
float density = context.getResources().getDisplayMetrics().density;
fill.setColor(style.getColor(Const.STYLE_FILL, R.color.circle_gray_fill));
fill.setStyle(Style.FILL);
Paint stroke = AndroidGraphicFactory.INSTANCE.createPaint();
stroke.setColor(style.getColor(Const.STYLE_STROKE, R.color.circle_gray_stroke));
stroke.setStrokeWidth(Math.max(1.5f * density, 1));
stroke.setStyle(Style.STROKE);
Circle circle = new Circle(latLong, acc, fill, stroke);
mapCircles.put(pr, circle);
layers.add(circle);
circle.setVisible(visible);
// Marker layer
Drawable drawable = style.getDrawable(Const.STYLE_MARKER);
Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(drawable);
Marker marker = new Marker(latLong, bitmap, 0, -bitmap.getHeight() * 10 / 24);
mapMarkers.put(pr, marker);
layers.add(marker);
marker.setVisible(visible);
style.recycle();
}
// move layers into view
updateMap();
}
/**
* Called when the source for the base map layer changes.
*
* This method destroys all tile layers and their associated tile caches while leaving overlays.
*/
void onMapSourceChanged() {
destroyLayers(false);
createLayers(false);
}
@Override
public void onPause() {
super.onPause();
if (mapDownloadLayer != null)
mapDownloadLayer.onPause();
}
@Override
public void onResume() {
super.onResume();
if (mapDownloadLayer != null)
mapDownloadLayer.onResume();
}
@Override
public void onStop() {
LatLong center = mapMap.getModel().mapViewPosition.getCenter();
byte zoom = mapMap.getModel().mapViewPosition.getZoomLevel();
SharedPreferences.Editor spEditor = mainActivity.mSharedPreferences.edit();
spEditor.putFloat(Const.KEY_PREF_MAP_LAT, (float) center.latitude);
spEditor.putFloat(Const.KEY_PREF_MAP_LON, (float) center.longitude);
spEditor.putInt(Const.KEY_PREF_MAP_ZOOM, zoom);
spEditor.commit();
super.onStop();
}
/**
* Updates the list of styles to use for the location providers.
*
* This method updates the internal list of styles to use for displaying
* locations on the map, assigning a style to each location provider.
* Styles that are defined in {@link SharedPreferences} are preserved. If
* none are defined, the GPS location provider is assigned the red style
* and the network location provider is assigned the blue style. The
* passive location provider is not assigned a style, as it does not send
* any locations of its own. Other location providers are assigned one of
* the following styles: green, orange, purple. If there are more location
* providers than styles, the same style (including red and blue) can be
* assigned to multiple providers. The mapping is written to
* SharedPreferences so that it will be preserved even as available
* location providers change.
*/
public void updateLocationProviderStyles() {
//FIXME: move code into assignLocationProviderStyle and use that
List allProviders = mainActivity.locationManager.getAllProviders();
allProviders.remove(LocationManager.PASSIVE_PROVIDER);
if (allProviders.contains(LocationManager.GPS_PROVIDER)) {
providerStyles.put(LocationManager.GPS_PROVIDER,
mainActivity.mSharedPreferences.getString(Const.KEY_PREF_LOC_PROV_STYLE + LocationManager.GPS_PROVIDER, Const.LOCATION_PROVIDER_RED));
mAvailableProviderStyles.remove(Const.LOCATION_PROVIDER_RED);
allProviders.remove(LocationManager.GPS_PROVIDER);
}
if (allProviders.contains(LocationManager.NETWORK_PROVIDER)) {
providerStyles.put(LocationManager.NETWORK_PROVIDER,
mainActivity.mSharedPreferences.getString(Const.KEY_PREF_LOC_PROV_STYLE + LocationManager.NETWORK_PROVIDER, Const.LOCATION_PROVIDER_BLUE));
mAvailableProviderStyles.remove(Const.LOCATION_PROVIDER_BLUE);
allProviders.remove(LocationManager.NETWORK_PROVIDER);
}
for (String prov : allProviders) {
if (mAvailableProviderStyles.isEmpty())
mAvailableProviderStyles.addAll(Arrays.asList(Const.LOCATION_PROVIDER_STYLES));
providerStyles.put(prov,
mainActivity.mSharedPreferences.getString(Const.KEY_PREF_LOC_PROV_STYLE + prov, mAvailableProviderStyles.get(0)));
mAvailableProviderStyles.remove(providerStyles.get(prov));
};
SharedPreferences.Editor spEditor = mainActivity.mSharedPreferences.edit();
for (String prov : providerStyles.keySet())
spEditor.putString(Const.KEY_PREF_LOC_PROV_STYLE + prov, providerStyles.get(prov));
spEditor.commit();
}
/**
* Updates the map view so that all markers are visible.
*/
public void updateMap() {
boolean needsRedraw = false;
Dimension dimension = mapMap.getModel().mapViewDimension.getDimension();
// just trigger a redraw if we're not going to pan or zoom
if ((dimension == null) || (!isMapViewAttached)) {
mapMap.getLayerManager().redrawLayers();
return;
}
// move locations into view and zoom out as needed
int tileSize = mapMap.getModel().displayModel.getTileSize();
BoundingBox bb = null;
BoundingBox bb2 = null;
for (Location l : providerLocations.values())
if ((l != null) && (l.getProvider() != "")) {
double lat = l.getLatitude();
double lon = l.getLongitude();
double yRadius = l.hasAccuracy()?((l.getAccuracy() * 360.0f) / Const.EARTH_CIRCUMFERENCE):0;
double xRadius = l.hasAccuracy()?(yRadius * Math.abs(Math.cos(lat))):0;
double minLon = Math.max(lon - xRadius, -180);
double maxLon = Math.min(lon + xRadius, 180);
double minLat = Math.max(lat - yRadius, -90);
double maxLat = Math.min(lat + yRadius, 90);
if (!isLocationStale(l)) {
// location is up to date, add to main BoundingBox
if (bb != null) {
minLat = Math.min(bb.minLatitude, minLat);
maxLat = Math.max(bb.maxLatitude, maxLat);
minLon = Math.min(bb.minLongitude, minLon);
maxLon = Math.max(bb.maxLongitude, maxLon);
}
bb = new BoundingBox(minLat, minLon, maxLat, maxLon);
} else {
// location is stale, add to stale BoundingBox
if (bb2 != null) {
minLat = Math.min(bb2.minLatitude, minLat);
maxLat = Math.max(bb2.maxLatitude, maxLat);
minLon = Math.min(bb2.minLongitude, minLon);
maxLon = Math.max(bb2.maxLongitude, maxLon);
}
bb2 = new BoundingBox(minLat, minLon, maxLat, maxLon);
}
}
if (bb == null) bb = bb2; // all locations are stale, center to them
if (bb == null) {
needsRedraw = true;
} else {
byte newZoom = LatLongUtils.zoomForBounds(dimension, bb, tileSize);
if (newZoom < 0)
newZoom = 0;
if (newZoom < mapMap.getModel().mapViewPosition.getZoomLevel()) {
mapMap.getModel().mapViewPosition.setZoomLevel(newZoom);
} else {
needsRedraw = true;
}
MapViewProjection proj = new MapViewProjection(mapMap);
Point nw = proj.toPixels(new LatLong(bb.maxLatitude, bb.minLongitude));
Point se = proj.toPixels(new LatLong(bb.minLatitude, bb.maxLongitude));
// move only if bb is not entirely visible
if ((nw.x < 0) || (nw.y < 0) || (se.x > dimension.width) || (se.y > dimension.height)) {
mapMap.getModel().mapViewPosition.setCenter(bb.getCenterPoint());
} else {
needsRedraw = true;
}
}
if (needsRedraw)
mapMap.getLayerManager().redrawLayers();
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/RadioSectionFragment.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
import static android.telephony.TelephonyManager.PHONE_TYPE_GSM;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.R;
import com.vonglasow.michael.satstat.data.CellTower;
import com.vonglasow.michael.satstat.data.CellTowerCdma;
import com.vonglasow.michael.satstat.data.CellTowerGsm;
import com.vonglasow.michael.satstat.data.CellTowerListCdma;
import com.vonglasow.michael.satstat.data.CellTowerListGsm;
import com.vonglasow.michael.satstat.data.CellTowerListLte;
import com.vonglasow.michael.satstat.data.CellTowerLte;
import com.vonglasow.michael.satstat.utils.WifiCapabilities;
import com.vonglasow.michael.satstat.utils.WifiScanResultComparator;
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.ScanResult;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TextView;
import android.widget.ImageView.ScaleType;
/**
* The fragment which displays radio network data.
*/
public class RadioSectionFragment extends Fragment {
public static final String TAG = "RadioSectionFragment";
/**
* The fragment argument representing the section number for this
* fragment.
*/
public static final String ARG_SECTION_NUMBER = "section_number";
private static final int NETWORK_REFRESH_DELAY = 1000; //the polling interval for the network type
private static final int WIFI_REFRESH_DELAY = 1000; //the time between two requests for WLAN rescan.
private MainActivity mainActivity = null;
private CellTower mServingCell;
private CellTowerListGsm mCellsGsm = new CellTowerListGsm();
private CellTowerListCdma mCellsCdma = new CellTowerListCdma();
private CellTowerListLte mCellsLte = new CellTowerListLte();
int mLastNetworkGen = 0; //the last observed (and displayed) network type
private int mLastCellAsu = NeighboringCellInfo.UNKNOWN_RSSI;
private int mLastCellDbm = CellTower.DBM_UNKNOWN;
private Handler networkTimehandler = null;
private Runnable networkTimeRunnable = null;
List scanResults = null;
WifiScanResultComparator wifiComparator;
private String selectedBSSID = "";
private Handler wifiTimehandler = null;
private Runnable wifiTimeRunnable = null;
private LinearLayout rilGsmLayout;
private TableLayout rilCells;
private LinearLayout rilCdmaLayout;
private TableLayout rilCdmaCells;
private LinearLayout rilLteLayout;
private TableLayout rilLteCells;
private LinearLayout wifiAps;
@SuppressLint("UseSparseArrays")
private final static HashMap channelsFrequency = new HashMap() {
/*
* Required for serializable objects
*/
private static final long serialVersionUID = 6793015643527778045L;
{
// 2.4 GHz (802.11 b/g/n)
this.put(2412, 1);
this.put(2417, 2);
this.put(2422, 3);
this.put(2427, 4);
this.put(2432, 5);
this.put(2437, 6);
this.put(2442, 7);
this.put(2447, 8);
this.put(2452, 9);
this.put(2457, 10);
this.put(2462, 11);
this.put(2467, 12);
this.put(2472, 13);
this.put(2484, 14);
//5 GHz (802.11 a/h/j/n/ac)
this.put(4915, 183);
this.put(4920, 184);
this.put(4925, 185);
this.put(4935, 187);
this.put(4940, 188);
this.put(4945, 189);
this.put(4960, 192);
this.put(4980, 196);
this.put(5035, 7);
this.put(5040, 8);
this.put(5045, 9);
this.put(5055, 11);
this.put(5060, 12);
this.put(5080, 16);
this.put(5170, 34);
this.put(5180, 36);
this.put(5190, 38);
this.put(5200, 40);
this.put(5210, 42);
this.put(5220, 44);
this.put(5230, 46);
this.put(5240, 48);
this.put(5260, 52);
this.put(5280, 56);
this.put(5300, 60);
this.put(5320, 64);
this.put(5500, 100);
this.put(5520, 104);
this.put(5540, 108);
this.put(5560, 112);
this.put(5580, 116);
this.put(5600, 120);
this.put(5620, 124);
this.put(5640, 128);
this.put(5660, 132);
this.put(5680, 136);
this.put(5700, 140);
this.put(5745, 149);
this.put(5765, 153);
this.put(5785, 157);
this.put(5805, 161);
this.put(5825, 165);
}
};
public RadioSectionFragment() {
}
private final void addWifiResult(ScanResult result) {
// needed to pass a persistent reference to the OnClickListener
final ScanResult r = result;
android.view.View.OnClickListener clis = new android.view.View.OnClickListener () {
@Override
public void onClick(View v) {
onWifiEntryClick(r.BSSID);
}
};
LinearLayout wifiLayout = new LinearLayout(wifiAps.getContext());
wifiLayout.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
wifiLayout.setOrientation(LinearLayout.HORIZONTAL);
wifiLayout.setWeightSum(22);
wifiLayout.setMeasureWithLargestChildEnabled(false);
ImageView wifiType = new ImageView(wifiAps.getContext());
wifiType.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.MATCH_PARENT, 3));
if (WifiCapabilities.isAdhoc(result)) {
wifiType.setImageResource(R.drawable.ic_content_wifi_adhoc);
} else if ((WifiCapabilities.isEnterprise(result)) || (WifiCapabilities.getScanResultSecurity(result) == WifiCapabilities.EAP)) {
wifiType.setImageResource(R.drawable.ic_content_wifi_eap);
} else if (WifiCapabilities.getScanResultSecurity(result) == WifiCapabilities.PSK) {
wifiType.setImageResource(R.drawable.ic_content_wifi_psk);
} else if (WifiCapabilities.getScanResultSecurity(result) == WifiCapabilities.WEP) {
wifiType.setImageResource(R.drawable.ic_content_wifi_wep);
} else if (WifiCapabilities.getScanResultSecurity(result) == WifiCapabilities.OPEN) {
wifiType.setImageResource(R.drawable.ic_content_wifi_open);
} else {
wifiType.setImageResource(R.drawable.ic_content_wifi_unknown);
}
wifiType.setScaleType(ScaleType.CENTER);
wifiLayout.addView(wifiType);
TableLayout wifiDetails = new TableLayout(wifiAps.getContext());
wifiDetails.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 19));
TableRow innerRow1 = new TableRow(wifiAps.getContext());
TextView newMac = new TextView(wifiAps.getContext());
newMac.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 14));
newMac.setTextAppearance(wifiAps.getContext(), android.R.style.TextAppearance_Medium);
newMac.setText(result.BSSID);
innerRow1.addView(newMac);
TextView newCh = new TextView(wifiAps.getContext());
newCh.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 2));
newCh.setTextAppearance(wifiAps.getContext(), android.R.style.TextAppearance_Medium);
newCh.setText(getChannelFromFrequency(result.frequency));
innerRow1.addView(newCh);
TextView newLevel = new TextView(wifiAps.getContext());
newLevel.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 3));
newLevel.setTextAppearance(wifiAps.getContext(), android.R.style.TextAppearance_Medium);
newLevel.setText(String.valueOf(result.level));
innerRow1.addView(newLevel);
innerRow1.setOnClickListener(clis);
wifiDetails.addView(innerRow1,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
TableRow innerRow2 = new TableRow(wifiAps.getContext());
TextView newSSID = new TextView(wifiAps.getContext());
newSSID.setLayoutParams(new TableRow.LayoutParams(0, LayoutParams.WRAP_CONTENT, 19));
newSSID.setTextAppearance(wifiAps.getContext(), android.R.style.TextAppearance_Small);
newSSID.setText(result.SSID);
innerRow2.addView(newSSID);
innerRow2.setOnClickListener(clis);
wifiDetails.addView(innerRow2, new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
wifiLayout.addView(wifiDetails);
wifiLayout.setOnClickListener(clis);
wifiAps.addView(wifiLayout);
}
/**
* Formats an item of cell information data for display.
*
* This helper function formats any item of cell information data, such as
* the cell ID, PSC or similar. For valid data a string with the properly
* formatted value will be returned. If the input value is
* {@link com.vonglasow.michael.satstat.data.CellTower#UNKNOWN}, then the
* {@code value_none} resource string will be returned.
* @param context the context of the caller
* @param format a format string, which must contain placeholders for exactly one variable, or {@code null}.
* @param raw the value to format
* @return
*/
public static String formatCellData(Context context, String format, int raw) {
if (raw == CellTower.UNKNOWN)
return context.getResources().getString(R.string.value_none);
else {
String fmt = (format != null) ? format : "%d";
return String.format(fmt, raw);
}
}
/**
* Formats cell signal strength for display.
*
* This helper function formats the signal strength for a cell. For valid
* data a string with the properly formatted value will be returned. If the
* input value is
* {@link com.vonglasow.michael.satstat.data.CellTower#DBM_UNKNOWN}, then
* the {@code value_none} resource string will be returned.
* @param context the context of the caller
* @param format a format string, which must contain placeholders for exactly one variable, or {@code null}.
* @param raw the signal strength in dBm
* @return
*/
public static String formatCellDbm(Context context, String format, int raw) {
if (raw == CellTower.DBM_UNKNOWN)
return context.getResources().getString(R.string.value_none);
else {
String fmt = (format != null) ? format : "%d";
return String.format(fmt, raw);
}
}
/**
* Gets the WiFi channel number for a frequency
* @param frequency The frequency in MHz
* @return The channel number corresponding to {@code frequency}
*/
public static String getChannelFromFrequency(int frequency) {
if (channelsFrequency.containsKey(frequency)) {
return String.valueOf(channelsFrequency.get(frequency));
}
else {
return "?";
}
}
/**
* Gets the display color for a phone network generation.
* @param generation The network generation, i.e. {@code 2}, {@code 3} or {@code 4} for any flavor of 2G, 3G or 4G, or {@code 0} for unknown
* @return The color in which to display the indicator. If {@code generation} is {@code 0} or not a valid generation, the color returned will be transparent.
*/
public static int getColorFromGeneration(int generation) {
switch (generation) {
case 2:
return(R.color.gen2);
case 3:
return(R.color.gen3);
case 4:
return(R.color.gen4);
default:
return(android.R.color.transparent);
}
}
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mainActivity = (MainActivity) this.getContext();
View rootView = inflater.inflate(R.layout.fragment_main_radio, container, false);
// Initialize controls
rilGsmLayout = (LinearLayout) rootView.findViewById(R.id.rilGsmLayout);
rilCells = (TableLayout) rootView.findViewById(R.id.rilCells);
rilCdmaLayout = (LinearLayout) rootView.findViewById(R.id.rilCdmaLayout);
rilCdmaCells = (TableLayout) rootView.findViewById(R.id.rilCdmaCells);
rilLteLayout = (LinearLayout) rootView.findViewById(R.id.rilLteLayout);
rilLteCells = (TableLayout) rootView.findViewById(R.id.rilLteCells);
wifiAps = (LinearLayout) rootView.findViewById(R.id.wifiAps);
rilGsmLayout.setVisibility(View.GONE);
rilCdmaLayout.setVisibility(View.GONE);
rilLteLayout.setVisibility(View.GONE);
networkTimehandler = new Handler();
networkTimeRunnable = new Runnable() {
@Override
public void run() {
int newNetworkType = mainActivity.telephonyManager.getNetworkType();
if (CellTower.getGenerationFromNetworkType(newNetworkType) != mLastNetworkGen)
onNetworkTypeChanged(newNetworkType);
else
networkTimehandler.postDelayed(this, NETWORK_REFRESH_DELAY);
}
};
wifiComparator = new WifiScanResultComparator();
wifiComparator.setCriterion(mainActivity.prefWifiSort);
wifiTimehandler = new Handler();
wifiTimeRunnable = new Runnable() {
@Override
public void run() {
mainActivity.wifiManager.startScan();
wifiTimehandler.postDelayed(this, WIFI_REFRESH_DELAY);
}
};
//get current phone info (first update won't fire until the cell actually changes)
if (ContextCompat.checkSelfPermission(mainActivity, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
updateCellData(null, null, null);
else
mainActivity.permsRequested[Const.PERM_REQUEST_CELL_INFO] = true;
//and make sure we have the correct network type
onNetworkTypeChanged(mainActivity.telephonyManager.getNetworkType());
mainActivity.wifiManager.startScan();
mainActivity.radioSectionFragment = this;
return rootView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mainActivity.radioSectionFragment == this)
mainActivity.radioSectionFragment = null;
}
/**
* Updates the network type indicator for the current cell. Called by
* {@link networkTimeRunnable.run()} or
* {@link android.telephony.PhoneStateListener#onDataConnectionStateChanged(int, int)}.
*
* @param networkType One of the NETWORK_TYPE_xxxx constants defined in {@link android.telephony.TelephonyManager}
*/
protected void onNetworkTypeChanged(int networkType) {
Log.d("MainActivity", "Network type changed to " + Integer.toString(networkType));
int newNetworkGen = CellTower.getGenerationFromNetworkType(networkType);
int oldNetworkGen = mLastNetworkGen;
if (newNetworkGen != mLastNetworkGen) {
networkTimehandler.removeCallbacks(networkTimeRunnable);
mLastNetworkGen = newNetworkGen;
/*
* Network type changes occur slightly before or after cell changes. Therefore, we may have
* stored cells in the wrong list when switching from or to LTE.
*/
if ((newNetworkGen == 4) || (oldNetworkGen == 4))
updateCellData(null, null, null);
else if (mServingCell != null) {
mServingCell.setNetworkType(networkType);
Log.d(MainActivity.class.getSimpleName(), String.format("Setting network type to %d for cell %s (%s)", mServingCell.getGeneration(), mServingCell.getText(), mServingCell.getAltText()));
}
}
showCells();
}
@Override
public void onResume() {
super.onResume();
wifiTimehandler.postDelayed(wifiTimeRunnable, WIFI_REFRESH_DELAY);
}
@Override
public void onStop() {
networkTimehandler.removeCallbacks(networkTimeRunnable);
wifiTimehandler.removeCallbacks(wifiTimeRunnable);
// we'll just skip that so locations will get invalidated in any case
//providerInvalidationHandler.removeCallbacksAndMessages(null);
super.onStop();
}
private final void onWifiEntryClick(String BSSID) {
selectedBSSID = BSSID;
refreshWifiResults();
}
final void refreshWifiResults() {
if (scanResults != null) {
wifiAps.removeAllViews();
wifiComparator.setCriterion(mainActivity.prefWifiSort);
Collections.sort(scanResults, wifiComparator);
//add the selected network first
for (ScanResult result : scanResults) {
if (result.BSSID.equals(selectedBSSID)) {
addWifiResult(result);
}
}
for (ScanResult result : scanResults) {
if (!result.BSSID.equals(selectedBSSID)) {
addWifiResult(result);
}
}
}
}
/**
* Updates the list of cells in range.
*
* This method is automatically called by
* {@link PhoneStateListener#onCellInfoChanged(List)}
* and {@link PhoneStateListener.onCellLocationChanged}. It must be called
* manually whenever {@link #mCellsCdma}, {@link #mCellsGsm},
* {@link #mCellsLte} or one of their values are modified, typically after
* calling {@link android.telephony.TelephonyManager#getAllCellInfo()},
* {@link android.telephony.TelephonyManager#getCellLocation()} or
* {@link android.telephony.TelephonyManager#getNeighboringCellInfo()}.
*/
protected void showCells() {
int cdmaVisibility = View.GONE;
int gsmVisibility = View.GONE;
int lteVisibility = View.GONE;
rilCells.removeAllViews();
if (mCellsGsm.containsValue(mServingCell)) {
showCellGsm((CellTowerGsm) mServingCell);
gsmVisibility = View.VISIBLE;
}
for (CellTowerGsm cell : mCellsGsm.getAll())
if (cell.hasSource() && (cell != mServingCell)) {
showCellGsm(cell);
gsmVisibility = View.VISIBLE;
}
rilGsmLayout.setVisibility(gsmVisibility);
rilCdmaCells.removeAllViews();
if (mCellsCdma.containsValue(mServingCell)) {
showCellCdma((CellTowerCdma) mServingCell);
cdmaVisibility = View.VISIBLE;
}
for (CellTowerCdma cell : mCellsCdma.getAll())
if (cell.hasSource() && (cell != mServingCell)) {
showCellCdma(cell);
cdmaVisibility = View.VISIBLE;
}
rilCdmaLayout.setVisibility(cdmaVisibility);
rilLteCells.removeAllViews();
if (mCellsLte.containsValue(mServingCell)) {
showCellLte((CellTowerLte) mServingCell);
lteVisibility = View.VISIBLE;
}
for (CellTowerLte cell : mCellsLte.getAll())
if (cell.hasSource() && (cell != mServingCell)) {
showCellLte(cell);
lteVisibility = View.VISIBLE;
}
rilLteLayout.setVisibility(lteVisibility);
}
protected void showCellCdma(CellTowerCdma cellTower) {
TableRow row = (TableRow) mainActivity.getLayoutInflater().inflate(R.layout.ril_cdma_list_item, null);
TextView type = (TextView) row.findViewById(R.id.type);
TextView sid = (TextView) row.findViewById(R.id.sid);
TextView nid = (TextView) row.findViewById(R.id.nid);
TextView bsid = (TextView) row.findViewById(R.id.bsid);
TextView dbm = (TextView) row.findViewById(R.id.dbm);
type.setTextColor(rilCdmaCells.getContext().getResources().getColor(getColorFromGeneration(cellTower.getGeneration())));
type.setText(rilCdmaCells.getContext().getResources().getString(R.string.smallDot));
sid.setText(formatCellData(rilCdmaCells.getContext(), null, cellTower.getSid()));
nid.setText(formatCellData(rilCdmaCells.getContext(), null, cellTower.getNid()));
bsid.setText(formatCellData(rilCdmaCells.getContext(), null, cellTower.getBsid()));
dbm.setText(formatCellDbm(rilCdmaCells.getContext(), null, cellTower.getDbm()));
rilCdmaCells.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
protected void showCellGsm(CellTowerGsm cellTower) {
TableRow row = (TableRow) mainActivity.getLayoutInflater().inflate(R.layout.ril_list_item, null);
TextView type = (TextView) row.findViewById(R.id.type);
TextView mcc = (TextView) row.findViewById(R.id.mcc);
TextView mnc = (TextView) row.findViewById(R.id.mnc);
TextView area = (TextView) row.findViewById(R.id.area);
TextView cell = (TextView) row.findViewById(R.id.cell);
TextView cell2 = (TextView) row.findViewById(R.id.cell2);
TextView unit = (TextView) row.findViewById(R.id.unit);
TextView dbm = (TextView) row.findViewById(R.id.dbm);
type.setTextColor(rilCells.getContext().getResources().getColor(getColorFromGeneration(cellTower.getGeneration())));
type.setText(rilCells.getContext().getResources().getString(R.string.smallDot));
mcc.setText(formatCellData(rilCells.getContext(), "%03d", cellTower.getMcc()));
mnc.setText(formatCellData(rilCells.getContext(), "%02d", cellTower.getMnc()));
area.setText(formatCellData(rilCells.getContext(), null, cellTower.getLac()));
int rtcid = cellTower.getCid() / 0x10000;
int cid = cellTower.getCid() % 0x10000;
if ((mainActivity.prefCid) && (cellTower.getCid() != CellTower.UNKNOWN) && (cellTower.getCid() > 0x0ffff)) {
cell.setText(String.format("%d-%d", rtcid, cid));
cell2.setText(formatCellData(rilCells.getContext(), null, cellTower.getCid()));
} else {
cell.setText(formatCellData(rilCells.getContext(), null, cellTower.getCid()));
cell2.setText(String.format("%d-%d", rtcid, cid));
}
cell2.setVisibility((mainActivity.prefCid2 && (cellTower.getCid() > 0x0ffff)) ? View.VISIBLE : View.GONE);
unit.setText(formatCellData(rilCells.getContext(), null, cellTower.getPsc()));
dbm.setText(formatCellDbm(rilCells.getContext(), null, cellTower.getDbm()));
rilCells.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
protected void showCellLte(CellTowerLte cellTower) {
TableRow row = (TableRow) mainActivity.getLayoutInflater().inflate(R.layout.ril_list_item, null);
TextView type = (TextView) row.findViewById(R.id.type);
TextView mcc = (TextView) row.findViewById(R.id.mcc);
TextView mnc = (TextView) row.findViewById(R.id.mnc);
TextView area = (TextView) row.findViewById(R.id.area);
TextView cell = (TextView) row.findViewById(R.id.cell);
TextView cell2 = (TextView) row.findViewById(R.id.cell2);
TextView unit = (TextView) row.findViewById(R.id.unit);
TextView dbm = (TextView) row.findViewById(R.id.dbm);
type.setTextColor(rilLteCells.getContext().getResources().getColor(getColorFromGeneration(cellTower.getGeneration())));
type.setText(rilLteCells.getContext().getResources().getString(R.string.smallDot));
mcc.setText(formatCellData(rilLteCells.getContext(), "%03d", cellTower.getMcc()));
mnc.setText(formatCellData(rilLteCells.getContext(), "%02d", cellTower.getMnc()));
area.setText(formatCellData(rilLteCells.getContext(), null, cellTower.getTac()));
int eNodeBId = cellTower.getCi() / 0x100;
int sectorId = cellTower.getCi() % 0x100;
if ((mainActivity.prefCid) && (cellTower.getCi() != CellTower.UNKNOWN)) {
cell.setText(String.format("%d-%d", eNodeBId, sectorId));
cell2.setText(formatCellData(rilLteCells.getContext(), null, cellTower.getCi()));
} else {
cell.setText(formatCellData(rilLteCells.getContext(), null, cellTower.getCi()));
cell2.setText(String.format("%d-%d", eNodeBId, sectorId));
}
cell2.setVisibility(mainActivity.prefCid2 ? View.VISIBLE : View.GONE);
unit.setText(formatCellData(rilLteCells.getContext(), null, cellTower.getPci()));
dbm.setText(formatCellDbm(rilLteCells.getContext(), null, cellTower.getDbm()));
rilLteCells.addView(row,new TableLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
}
/**
* Updates all cell data.
*
* This method is called whenever any change in the cell environment (cells in view or signal
* strengths) is signaled, e.g. by a call to a {@link android.telephony.PhoneStateListener}. The
* arguments of this method should be filled with the data passed to the
* {@link android.telephony.PhoneStateListener} where possible, and null passed for all others.
*
* To force an update of all cell data, simply call this method with each argument set to null.
*
* If any of the arguments is null, this method will try to obtain that data by querying
* {@link android.telephony.TelephonyManager}. The only exception is {@code signalStrength}, which
* will not be explicitly queried if missing.
*
* It will first process {@code aCellInfo}, then {@code aLocation}, querying current values from
* {@link android.telephony.TelephonyManager} if one of these arguments is null. Next it will process
* {@code signalStrength}, if supplied, and eventually obtain neighboring cells by calling
* {@link android.telephony.TelephonyManager#getNeighboringCellInfo()} and process these. Eventually
* it will refresh the list of cells.
*
* @param aLocation The {@link android.telephony.CellLocation} reported by a
* {@link android.telephony.PhoneStateListener}. If null, the current value will be queried.
* @param aSignalStrength The {@link android.telephony.SignalStrength} reported by a
* {@link android.telephony.PhoneStateListener}. If null, the signal strength of the serving cell
* will either be taken from {@code aCellInfo}, if available, or not be updated at all.
* @param aCellInfo A list of {@link android.telephony.CellInfo} instances reported by a
* {@link android.telephony.PhoneStateListener}. If null, the current value will be queried.
*/
@SuppressLint("NewApi")
public void updateCellData(CellLocation aLocation, SignalStrength signalStrength, List aCellInfo) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
try {
/*
* CellInfo requires API 17+ and should in theory return all cells in view. In practice,
* some devices do not implement it or return only a partial list. On some devices,
* PhoneStateListener#onCellInfoChanged() will fire but always receive a null argument.
*/
List cellInfo = (aCellInfo != null) ? aCellInfo : mainActivity.telephonyManager.getAllCellInfo();
mCellsGsm.updateAll(cellInfo);
mCellsCdma.updateAll(cellInfo);
mCellsLte.updateAll(cellInfo);
} catch (SecurityException e) {
// Permission not granted, can't retrieve cell data
Log.w(TAG, "Permission not granted, TelephonyManager#getAllCellInfo() failed");
}
}
try {
/*
* CellLocation should return the serving cell, unless it is LTE (in which case it should
* return null). In practice, however, some devices do return LTE cells. The approach of
* this method does not work well for devices with multiple radios.
*/
CellLocation location = (aLocation != null) ? aLocation : mainActivity.telephonyManager.getCellLocation();
String networkOperator = mainActivity.telephonyManager.getNetworkOperator();
mCellsGsm.removeSource(CellTower.SOURCE_CELL_LOCATION);
mCellsCdma.removeSource(CellTower.SOURCE_CELL_LOCATION);
mCellsLte.removeSource(CellTower.SOURCE_CELL_LOCATION);
if (location instanceof GsmCellLocation) {
if (mLastNetworkGen < 4) {
mServingCell = mCellsGsm.update(networkOperator, (GsmCellLocation) location);
if ((mServingCell.getDbm() == CellTower.DBM_UNKNOWN) && (mServingCell instanceof CellTowerGsm))
((CellTowerGsm) mServingCell).setAsu(mLastCellAsu);
} else {
mServingCell = mCellsLte.update(networkOperator, (GsmCellLocation) location);
if (mServingCell.getDbm() == CellTower.DBM_UNKNOWN)
((CellTowerLte) mServingCell).setAsu(mLastCellAsu);
}
} else if (location instanceof CdmaCellLocation) {
mServingCell = mCellsCdma.update((CdmaCellLocation) location);
if (mServingCell.getDbm() == CellTower.DBM_UNKNOWN)
((CellTowerCdma) mServingCell).setDbm(mLastCellDbm);
}
networkTimehandler.removeCallbacks(networkTimeRunnable);
} catch (SecurityException e) {
// Permission not granted, can't retrieve cell data
Log.w(TAG, "Permission not granted, cannot retrieve cell location");
}
if ((mServingCell == null) || (mServingCell.getGeneration() <= 0)) {
if ((mLastNetworkGen != 0) && (mServingCell != null))
mServingCell.setGeneration(mLastNetworkGen);
NetworkInfo netinfo = mainActivity.connectivityManager.getActiveNetworkInfo();
if ((netinfo == null)
|| (netinfo.getType() < ConnectivityManager.TYPE_MOBILE_MMS)
|| (netinfo.getType() > ConnectivityManager.TYPE_MOBILE_HIPRI)) {
networkTimehandler.postDelayed(networkTimeRunnable, NETWORK_REFRESH_DELAY);
}
} else if (mServingCell != null) {
mLastNetworkGen = mServingCell.getGeneration();
}
if ((signalStrength != null) && (mServingCell != null)) {
int pt = mainActivity.telephonyManager.getPhoneType();
if (pt == PHONE_TYPE_GSM) {
mLastCellAsu = signalStrength.getGsmSignalStrength();
updateNeighboringCellInfo();
if (mServingCell instanceof CellTowerGsm)
((CellTowerGsm) mServingCell).setAsu(mLastCellAsu);
else
Log.w(MainActivity.class.getSimpleName(),
"Got SignalStrength for PHONE_TYPE_GSM but serving cell is not GSM");
} else if (pt == PHONE_TYPE_CDMA) {
mLastCellDbm = signalStrength.getCdmaDbm();
if ((mServingCell != null) && (mServingCell instanceof CellTowerCdma))
mServingCell.setDbm(mLastCellDbm);
else
Log.w(MainActivity.class.getSimpleName(),
"Got SignalStrength for PHONE_TYPE_CDMA but serving cell is not CDMA");
} else
Log.w(MainActivity.class.getSimpleName(),
String.format("Got SignalStrength for unknown phone type (%d)", pt));
} else if (mServingCell == null) {
Log.w(MainActivity.class.getSimpleName(),
"Got SignalStrength but serving cell is null");
}
updateNeighboringCellInfo();
showCells();
}
/**
* Requeries neighboring cells
*/
protected void updateNeighboringCellInfo() {
try {
/*
* NeighboringCellInfo is not supported on some devices and will return no data. It lists
* only GSM and successors' cells, but not CDMA cells.
*/
List neighboringCells = mainActivity.telephonyManager.getNeighboringCellInfo();
String networkOperator = mainActivity.telephonyManager.getNetworkOperator();
mCellsGsm.updateAll(networkOperator, neighboringCells);
mCellsLte.updateAll(networkOperator, neighboringCells);
} catch (SecurityException e) {
// Permission not granted, can't retrieve cell data
Log.w(TAG, "Permission not granted, cannot get neighboring cell info");
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/SensorSectionFragment.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import static android.hardware.SensorManager.SENSOR_STATUS_ACCURACY_HIGH;
import static android.hardware.SensorManager.SENSOR_STATUS_ACCURACY_LOW;
import static android.hardware.SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM;
import static android.hardware.SensorManager.SENSOR_STATUS_UNRELIABLE;
import com.vonglasow.michael.satstat.R;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* The fragment which displays sensor data.
*/
public class SensorSectionFragment extends Fragment {
public static final String TAG = "SensorSectionFragment";
/**
* The fragment argument representing the section number for this
* fragment.
*/
public static final String ARG_SECTION_NUMBER = "section_number";
private MainActivity mainActivity = null;
/*
* Maximum resolutions for sensors, expressed as number of decimals. These
* values were chosen based on screen real estate and significance. They
* may be lowered if actual precision is lower, but will not be increased
* even if sensors are capable of delivering higher precision.
*/
private byte mAccSensorRes = 3;
private byte mGyroSensorRes = 4;
private byte mMagSensorRes = 2;
private byte mLightSensorRes = 1;
private byte mProximitySensorRes = 1;
private byte mPressureSensorRes = 0;
private byte mHumiditySensorRes = 0;
private byte mTempSensorRes = 1;
private TextView accStatus;
private TextView accHeader;
private TextView accTotal;
private TextView accX;
private TextView accY;
private TextView accZ;
private TextView rotStatus;
private TextView rotHeader;
private TextView rotTotal;
private TextView rotX;
private TextView rotY;
private TextView rotZ;
private TextView magStatus;
private TextView magHeader;
private TextView magTotal;
private TextView magX;
private TextView magY;
private TextView magZ;
private TextView orStatus;
private TextView orHeader;
private TextView orAzimuth;
private TextView orAziText;
private TextView orPitch;
private TextView orRoll;
private TextView miscHeader;
private TextView tempStatus;
private TextView tempHeader;
private TextView metTemp;
private TextView pressureStatus;
private TextView pressureHeader;
private TextView metPressure;
private TextView humidStatus;
private TextView humidHeader;
private TextView metHumid;
private TextView lightStatus;
private TextView lightHeader;
private TextView light;
private TextView proximityStatus;
private TextView proximityHeader;
private TextView proximity;
public SensorSectionFragment() {
}
/**
* Converts an accuracy value into a color identifier.
*/
public static int accuracyToColor(int accuracy) {
switch (accuracy) {
case SENSOR_STATUS_ACCURACY_HIGH:
return(R.color.accHigh);
case SENSOR_STATUS_ACCURACY_MEDIUM:
return(R.color.accMedium);
case SENSOR_STATUS_ACCURACY_LOW:
return(R.color.accLow);
case SENSOR_STATUS_UNRELIABLE:
return(R.color.accUnreliable);
default:
return(android.R.color.background_dark);
}
}
/**
* Gets the number of decimal digits to show when displaying sensor values, based on sensor accuracy.
* @param sensor The sensor
* @param maxDecimals The maximum number of decimals to display, even if the sensor's accuracy is higher
* @return
*/
public static byte getSensorDecimals(Sensor sensor, byte maxDecimals) {
if (sensor == null) return 0;
float res = sensor.getResolution();
if (res == 0) return maxDecimals;
return (byte) Math.min(maxDecimals,
(sensor != null) ? (byte) Math.max(Math.ceil(
(float) -Math.log10(sensor.getResolution())), 0) : 0);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mainActivity = (MainActivity) this.getContext();
View rootView = inflater.inflate(R.layout.fragment_main_sensors, container, false);
Sensor mAccSensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Sensor mGyroSensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
Sensor mMagSensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
Sensor mLightSensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
Sensor mProximitySensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
Sensor mPressureSensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
Sensor mHumiditySensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
Sensor mTempSensor = mainActivity.sensorManager.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
mAccSensorRes = getSensorDecimals(mAccSensor, mAccSensorRes);
mGyroSensorRes = getSensorDecimals(mGyroSensor, mGyroSensorRes);
mMagSensorRes = getSensorDecimals(mMagSensor, mMagSensorRes);
mLightSensorRes = getSensorDecimals(mLightSensor, mLightSensorRes);
mProximitySensorRes = getSensorDecimals(mProximitySensor, mProximitySensorRes);
mPressureSensorRes = getSensorDecimals(mPressureSensor, mPressureSensorRes);
mHumiditySensorRes = getSensorDecimals(mHumiditySensor, mHumiditySensorRes);
mTempSensorRes = getSensorDecimals(mTempSensor, mTempSensorRes);
// Initialize controls
accStatus = (TextView) rootView.findViewById(R.id.accStatus);
accHeader = (TextView) rootView.findViewById(R.id.accHeader);
accX = (TextView) rootView.findViewById(R.id.accX);
accY = (TextView) rootView.findViewById(R.id.accY);
accZ = (TextView) rootView.findViewById(R.id.accZ);
accTotal = (TextView) rootView.findViewById(R.id.accTotal);
rotStatus = (TextView) rootView.findViewById(R.id.rotStatus);
rotHeader = (TextView) rootView.findViewById(R.id.rotHeader);
rotX = (TextView) rootView.findViewById(R.id.rotX);
rotY = (TextView) rootView.findViewById(R.id.rotY);
rotZ = (TextView) rootView.findViewById(R.id.rotZ);
rotTotal = (TextView) rootView.findViewById(R.id.rotTotal);
magStatus = (TextView) rootView.findViewById(R.id.magStatus);
magHeader = (TextView) rootView.findViewById(R.id.magHeader);
magX = (TextView) rootView.findViewById(R.id.magX);
magY = (TextView) rootView.findViewById(R.id.magY);
magZ = (TextView) rootView.findViewById(R.id.magZ);
magTotal = (TextView) rootView.findViewById(R.id.magTotal);
orStatus = (TextView) rootView.findViewById(R.id.orStatus);
orHeader = (TextView) rootView.findViewById(R.id.orHeader);
orAzimuth = (TextView) rootView.findViewById(R.id.orAzimuth);
orAziText = (TextView) rootView.findViewById(R.id.orAziText);
orPitch = (TextView) rootView.findViewById(R.id.orPitch);
orRoll = (TextView) rootView.findViewById(R.id.orRoll);
miscHeader = (TextView) rootView.findViewById(R.id.miscHeader);
tempStatus = (TextView) rootView.findViewById(R.id.tempStatus);
tempHeader = (TextView) rootView.findViewById(R.id.tempHeader);
metTemp = (TextView) rootView.findViewById(R.id.metTemp);
pressureStatus = (TextView) rootView.findViewById(R.id.pressureStatus);
pressureHeader = (TextView) rootView.findViewById(R.id.pressureHeader);
metPressure = (TextView) rootView.findViewById(R.id.metPressure);
humidStatus = (TextView) rootView.findViewById(R.id.humidStatus);
humidHeader = (TextView) rootView.findViewById(R.id.humidHeader);
metHumid = (TextView) rootView.findViewById(R.id.metHumid);
lightStatus = (TextView) rootView.findViewById(R.id.lightStatus);
lightHeader = (TextView) rootView.findViewById(R.id.lightHeader);
light = (TextView) rootView.findViewById(R.id.light);
proximityStatus = (TextView) rootView.findViewById(R.id.proximityStatus);
proximityHeader = (TextView) rootView.findViewById(R.id.proximityHeader);
proximity = (TextView) rootView.findViewById(R.id.proximity);
mainActivity.sensorSectionFragment = this;
return rootView;
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (mainActivity.sensorSectionFragment == this)
mainActivity.sensorSectionFragment = null;
}
/**
* Called by {@link MainActivity} when a sensor's reading changes. Updates sensor display.
*/
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
accX.setText(String.format("%." + mAccSensorRes + "f", event.values[0]));
accY.setText(String.format("%." + mAccSensorRes + "f", event.values[1]));
accZ.setText(String.format("%." + mAccSensorRes + "f", event.values[2]));
accTotal.setText(String.format("%." + mAccSensorRes + "f", Math.sqrt(Math.pow(event.values[0], 2) + Math.pow(event.values[1], 2) + Math.pow(event.values[2], 2))));
accStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_ORIENTATION:
orAzimuth.setText(String.format("%.0f%s", event.values[0], getString(R.string.unit_degree)));
orAziText.setText(MainActivity.formatOrientation(this.getContext(), event.values[0]));
orPitch.setText(String.format("%.0f%s", event.values[1], getString(R.string.unit_degree)));
orRoll.setText(String.format("%.0f%s", event.values[2], getString(R.string.unit_degree)));
orStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_GYROSCOPE:
rotX.setText(String.format("%." + mGyroSensorRes + "f", event.values[0]));
rotY.setText(String.format("%." + mGyroSensorRes + "f", event.values[1]));
rotZ.setText(String.format("%." + mGyroSensorRes + "f", event.values[2]));
rotTotal.setText(String.format("%." + mGyroSensorRes + "f", Math.sqrt(Math.pow(event.values[0], 2) + Math.pow(event.values[1], 2) + Math.pow(event.values[2], 2))));
rotStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_MAGNETIC_FIELD:
magX.setText(String.format("%." + mMagSensorRes + "f", event.values[0]));
magY.setText(String.format("%." + mMagSensorRes + "f", event.values[1]));
magZ.setText(String.format("%." + mMagSensorRes + "f", event.values[2]));
magTotal.setText(String.format("%." + mMagSensorRes + "f", Math.sqrt(Math.pow(event.values[0], 2) + Math.pow(event.values[1], 2) + Math.pow(event.values[2], 2))));
magStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_LIGHT:
light.setText(String.format("%." + mLightSensorRes + "f", event.values[0]));
lightStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_PROXIMITY:
proximity.setText(String.format("%." + mProximitySensorRes + "f", event.values[0]));
proximityStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_PRESSURE:
metPressure.setText(String.format("%." + mPressureSensorRes + "f", event.values[0]));
pressureStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_RELATIVE_HUMIDITY:
metHumid.setText(String.format("%." + mHumiditySensorRes + "f", event.values[0]));
humidStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
case Sensor.TYPE_AMBIENT_TEMPERATURE:
metTemp.setText(String.format("%." + mTempSensorRes + "f", event.values[0]));
tempStatus.setTextColor(getResources().getColor(accuracyToColor(event.accuracy)));
break;
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/ui/SettingsActivity.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.ui;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.mapsforge.map.android.util.AndroidUtil;
import org.mapsforge.map.layer.cache.TileCache;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.PasvLocListenerService;
import com.vonglasow.michael.satstat.R;
import android.Manifest;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Toast;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
public class SettingsActivity extends AppCompatActivity implements OnPreferenceClickListener, OnSharedPreferenceChangeListener{
public static final String TAG = SettingsActivity.class.getSimpleName();
public static final int REQUEST_CODE_PICK_MAP_PATH = 1;
/**
* A string array that specifies the name of the intent to use, and the scheme to use with it
* when setting the data for the intent.
*
* @author k9mail, mvglasow
*/
private static final String[][] PICK_DIRECTORY_INTENTS = {
{ Intent.ACTION_PICK, "folder://" }, // CM File Manager, Blackmoon File Browser, possibly others
{ "org.openintents.action.PICK_DIRECTORY", "file://" }, // OI File Manager, possibly others
{ "com.estrongs.action.PICK_DIRECTORY", "file://" }, // ES File Explorer
{ "com.androidworkz.action.PICK_DIRECTORY", "file://" }
};
private SharedPreferences mSharedPreferences;
Preference prefMapPath;
String prefMapPathValue = Const.MAP_PATH_DEFAULT;
Preference prefMapDownload;
Preference prefMapPurge;
@SuppressLint("NewApi")
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE_PICK_MAP_PATH) {
if (resultCode == RESULT_OK) {
setMapPath(data.getData().getPath());
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
// Show the Up button in the action bar.
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(android.R.id.content, new SettingsFragment())
.commit();
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
// some logic to use the pre-1.7 setting KEY_PREF_UPDATE_WIFI as a
// fallback if KEY_PREF_UPDATE_NETWORKS is not set
if (!mSharedPreferences.contains(Const.KEY_PREF_UPDATE_NETWORKS)) {
Set fallbackUpdateNetworks = new HashSet();
if (mSharedPreferences.getBoolean(Const.KEY_PREF_UPDATE_WIFI, false)) {
fallbackUpdateNetworks.add(Const.KEY_PREF_UPDATE_NETWORKS_WIFI);
}
SharedPreferences.Editor spEditor = mSharedPreferences.edit();
spEditor.putStringSet(Const.KEY_PREF_UPDATE_NETWORKS, fallbackUpdateNetworks);
spEditor.commit();
}
// by default, show GPS and network location in map
if (!mSharedPreferences.contains(Const.KEY_PREF_LOC_PROV)) {
Set defaultLocProvs = new HashSet(Arrays.asList(new String[] {LocationManager.GPS_PROVIDER, LocationManager.NETWORK_PROVIDER}));
SharedPreferences.Editor spEditor = mSharedPreferences.edit();
spEditor.putStringSet(Const.KEY_PREF_LOC_PROV, defaultLocProvs);
spEditor.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* @author k9mail, mvglasow
*/
@Override
public boolean onPreferenceClick(Preference preference) {
if (preference == prefMapPath) {
boolean success = false;
int i = 0;
do {
String intentAction = PICK_DIRECTORY_INTENTS[i][0];
String uriPrefix = PICK_DIRECTORY_INTENTS[i][1];
Intent intent = new Intent(intentAction);
if (uriPrefix != null)
intent.setData(Uri.parse(uriPrefix + prefMapPathValue));
try {
startActivityForResult(intent, REQUEST_CODE_PICK_MAP_PATH);
Log.i("SettingsActivity", String.format("Sending intent: %s", intentAction));
success = true;
} catch (ActivityNotFoundException e) {
// Try the next intent in the list
i++;
}
} while (!success && (i < PICK_DIRECTORY_INTENTS.length));
if (!success) {
//No app for folder browsing is installed, show a fallback dialog
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.pref_map_path));
LayoutInflater inflater = LayoutInflater.from(this);
final View alertView = inflater.inflate(R.layout.alert_map_path, null);
final EditText editPath = (EditText) alertView.findViewById(R.id.editPath);
editPath.setText(prefMapPathValue);
final ImageButton btnOiFilemanager = (ImageButton) alertView.findViewById(R.id.btn_oi_filemanager);
btnOiFilemanager.setTag(Uri.parse("market://details?id=org.openintents.filemanager"));
final ImageButton btnCmFilemanager = (ImageButton) alertView.findViewById(R.id.btn_cm_filemanager);
btnCmFilemanager.setTag(Uri.parse("market://details?id=com.cyanogenmod.filemanager.ics"));
final View.OnClickListener clickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (v.getTag() instanceof Uri)
startActivity(new Intent(Intent.ACTION_VIEW, (Uri) v.getTag()));
}
};
builder.setView(alertView);
btnOiFilemanager.setOnClickListener(clickListener);
btnCmFilemanager.setOnClickListener(clickListener);
builder.setPositiveButton(getString(R.string.action_ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
setMapPath(editPath.getText().toString());
}
});
builder.setNegativeButton(getString(R.string.action_cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// NOP
}
});
builder.show();
success = true;
}
return success;
} else if (preference == prefMapDownload) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)
startActivity(new Intent(this, MapDownloadActivity.class));
else
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, Const.PERM_REQUEST_MAP_DOWNLOAD);
return true;
} else if (preference == prefMapPurge) {
TileCache mapRendererTileCache = AndroidUtil.createExternalStorageTileCache(this,
Const.TILE_CACHE_INTERNAL_RENDER_THEME, 0, 256, true);
TileCache mapDownloadTileCache = AndroidUtil.createExternalStorageTileCache(this,
Const.TILE_CACHE_OSM, 0, 256, true);
mapRendererTileCache.purge();
mapDownloadTileCache.purge();
mapRendererTileCache.destroy();
mapDownloadTileCache.destroy();
/*
* This is a hack to have the map view (if it is active) redraw the map:
* Setting the preference to true causes the listener in MainView to fire.
* The listener will, in turn, determine if a map view is active and, if so,
* cause it to reload all tile layers, then reset the preference to false.
*/
SharedPreferences.Editor spEditor = mSharedPreferences.edit();
spEditor.putBoolean(Const.KEY_PREF_MAP_PURGE, true);
spEditor.commit();
String message = getString(R.string.status_map_purged);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
return true;
} else
return false;
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if ((requestCode == Const.PERM_REQUEST_MAP_DOWNLOAD) && (grantResults.length > 0)) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED)
startActivity(new Intent(this, MapDownloadActivity.class));
else {
String message = getString(R.string.status_perm_map_download);
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
} else if ((requestCode == Const.PERM_REQUEST_LOCATION_PREF) && (grantResults.length > 0)) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(Const.PERM_REQUEST_LOCATION_NOTIFICATION);
} else
Log.i(TAG, "ACCESS_FINE_LOCATION permission not granted, a notification will appear when it is needed");
}
}
@Override
protected void onResume() {
super.onResume();
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
SettingsFragment sf = (SettingsFragment) getFragmentManager().findFragmentById(android.R.id.content);
Preference prefUpdateLast = sf.findPreference(Const.KEY_PREF_UPDATE_LAST);
final long value = mSharedPreferences.getLong(Const.KEY_PREF_UPDATE_LAST, 0);
prefUpdateLast.setSummary(String.format(getString(R.string.pref_lastupdate_summary), value));
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
boolean needsLocationPerm = false;
if (key.equals(Const.KEY_PREF_NOTIFY_FIX) || key.equals(Const.KEY_PREF_NOTIFY_SEARCH)) {
boolean notifyFix = sharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_FIX, false);
boolean notifySearch = sharedPreferences.getBoolean(Const.KEY_PREF_NOTIFY_SEARCH, false);
if (!(notifyFix || notifySearch)) {
Intent stopServiceIntent = new Intent(this, PasvLocListenerService.class);
this.stopService(stopServiceIntent);
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
needsLocationPerm = true;
}
} else if (key.equals(Const.KEY_PREF_UPDATE_FREQ)) {
// this piece of code is necessary because Android has no way
// of updating the preference summary automatically. I am
// told the absence of such functionality is a feature...
SettingsFragment sf = (SettingsFragment) getFragmentManager().findFragmentById(android.R.id.content);
ListPreference prefUpdateFreq = (ListPreference) sf.findPreference(Const.KEY_PREF_UPDATE_FREQ);
final String value = sharedPreferences.getString(key, key);
final int index = prefUpdateFreq.findIndexOfValue(value);
if (index >= 0) {
final String summary = (String)prefUpdateFreq.getEntries()[index];
prefUpdateFreq.setSummary(summary);
}
} else if (key.equals(Const.KEY_PREF_MAP_PATH)) {
SettingsFragment sf = (SettingsFragment) getFragmentManager().findFragmentById(android.R.id.content);
Preference prefMapPath = sf.findPreference(Const.KEY_PREF_MAP_PATH);
prefMapPathValue = mSharedPreferences.getString(Const.KEY_PREF_MAP_PATH, prefMapPathValue);
prefMapPath.setSummary(prefMapPathValue);
} else if (key.equals(Const.KEY_PREF_UPDATE_NETWORKS)) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
Set updateNetworks = sharedPreferences.getStringSet(Const.KEY_PREF_UPDATE_NETWORKS, new HashSet());
if (!updateNetworks.isEmpty())
needsLocationPerm = true;
}
}
if (needsLocationPerm)
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, Const.PERM_REQUEST_LOCATION_PREF);
}
@Override
protected void onStart() {
super.onStart();
SettingsFragment sf = (SettingsFragment) getFragmentManager().findFragmentById(android.R.id.content);
prefMapPath = sf.findPreference(Const.KEY_PREF_MAP_PATH);
prefMapPathValue = mSharedPreferences.getString(Const.KEY_PREF_MAP_PATH, prefMapPathValue);
prefMapPath.setSummary(prefMapPathValue);
prefMapPath.setOnPreferenceClickListener(this);
prefMapDownload = sf.findPreference(Const.KEY_PREF_MAP_DOWNLOAD);
prefMapDownload.setOnPreferenceClickListener(this);
prefMapPurge = sf.findPreference(Const.KEY_PREF_MAP_PURGE);
prefMapPurge.setOnPreferenceClickListener(this);
}
@Override
protected void onStop() {
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
super.onStop();
}
protected void setMapPath(String path) {
SharedPreferences.Editor spEditor = mSharedPreferences.edit();
spEditor.putString(Const.KEY_PREF_MAP_PATH, path);
spEditor.commit();
}
public static class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/DownloadTreeViewAdapter.java
================================================
/*
* Copyright (c) 2011, Polidea
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import com.vonglasow.michael.satstat.Const;
import com.vonglasow.michael.satstat.R;
import com.vonglasow.michael.satstat.ui.MapDownloadActivity;
import pl.polidea.treeview.AbstractTreeViewAdapter;
import pl.polidea.treeview.TreeNodeInfo;
import pl.polidea.treeview.TreeStateManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
/**
* This is a very simple adapter that provides very basic tree view with a
* simple item description.
*
*/
public class DownloadTreeViewAdapter extends AbstractTreeViewAdapter implements RemoteDirListListener {
private static final String TAG = DownloadTreeViewAdapter.class.getSimpleName();
/**
* Progress update delay in milliseconds
*/
private static final int PROGRESS_DELAY = 1000;
TreeStateManager manager;
Map listTasks;
Map downloadsByReference;
Map downloadsByUri;
Map downloadsByFile;
DownloadManager downloadManager;
SharedPreferences sharedPreferences;
Bundle savedInstanceState = null;
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
Handler handler = new Handler();
private boolean isProgressCheckerRunning = false;
private boolean isReleased = true;
/**
*
* @param activity
* @param treeStateManager
* @param numberOfLevels
*/
public DownloadTreeViewAdapter(final Activity activity,
final TreeStateManager treeStateManager,
final int numberOfLevels) {
super(activity, treeStateManager, numberOfLevels);
this.manager = treeStateManager;
listTasks = new HashMap();
downloadsByReference = new HashMap();
downloadsByUri = new HashMap();
downloadsByFile = new HashMap();
df.setTimeZone(TimeZone.getDefault());
downloadManager = (DownloadManager) activity.getSystemService(Context.DOWNLOAD_SERVICE);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity);
checkProgress();
if (!downloadsByReference.isEmpty())
startProgressChecker();
}
/**
* Checks download progress.
*
* This will populate the download lists with any items that are missing, which would be the case if
* downloads are already in progress as the Activity is started, or if the download manager has renamed
* the downloaded file to avoid overwriting an existing one.
*/
private void checkProgress() {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterByStatus(~(DownloadManager.STATUS_FAILED | DownloadManager.STATUS_SUCCESSFUL));
Cursor cursor = downloadManager.query(query);
if (!cursor.moveToFirst()) {
cursor.close();
return;
}
do {
/*
* The download manager will not supply a local file name until the download has started.
* We therefore need to cover all possible cases:
* - No DownloadInfo yet (file objects may or may not be null)
* - DownloadInfo exists but has no files
* - DownloadInfo exists but does not have the file actually used by the download manager
*/
DownloadInfo info;
Long reference = cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
Uri uri = Uri.parse(cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_URI)));
String downloadFileName = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
File downloadFile = (downloadFileName != null) ? new File(downloadFileName) : null;
File targetFile = (downloadFile != null) ? new File(downloadFile.getParent(), uri.getLastPathSegment()) : null;
int progress = (int) (cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) / 1024);
if (downloadsByReference.containsKey(reference)) {
info = downloadsByReference.get(reference);
if (downloadFile != null) {
if ((info.downloadFile == null) || (!info.downloadFile.equals(info.targetFile)))
info.downloadFile = downloadFile;
if (info.targetFile == null)
info.targetFile = targetFile;
}
info.progress = progress;
} else {
info = new DownloadInfo(uri, targetFile, downloadFile, reference, progress);
downloadsByReference.put(info.reference, info);
downloadsByUri.put(info.uri, info);
if (info.targetFile != null)
downloadsByFile.put(info.targetFile, info);
}
if ((downloadFile != null) && (!downloadsByFile.containsKey(downloadFile))) {
info.downloadFile = downloadFile;
downloadsByFile.put(info.downloadFile, info);
}
} while (cursor.moveToNext());
cursor.close();
}
/**
* Registers the receiver for download events.
*/
public void registerIntentReceiver() {
getActivity().getApplicationContext().registerReceiver(downloadReceiver, new IntentFilter(Const.DOWNLOAD_RECEIVER_REGISTERED));
getActivity().getApplicationContext().registerReceiver(downloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
getActivity().getApplicationContext().registerReceiver(downloadReceiver, new IntentFilter(DownloadManager.ACTION_NOTIFICATION_CLICKED));
isReleased = false;
/*
* Send a DOWNLOAD_RECEIVER_REGISTERED broadcast, causing all released receivers to unregister.
* Since sendBroadcast() is asynchronous, this may not be 100% safe against race conditions: if a
* download finishes (or the download notification gets clicked) in the short window between sending
* and receiving the broadcast, the intent for the download would be processed twice. This is not
* a problem as the related code is safe to run multiple times as long as this happens in an atomic
* manner, which is the case as long as they are run from the main thread's queue.
*/
Intent registeredIntent = new Intent(Const.DOWNLOAD_RECEIVER_REGISTERED);
getActivity().sendBroadcast(registeredIntent);
}
/**
* Notifies the {@code DownloadTreeViewAdapter} that the caller no longer needs the receiver.
*
* Calling this method will unregister the receiver only if no downloads are currently in progress. If
* downloads are in progress, a flag will be set, causing the receiver to be unregistered after the last
* download has finished.
*
* This will also stop the progress checker.
*/
public void releaseIntentReceiver() {
isReleased = true;
stopProgressChecker();
if (downloadsByUri.isEmpty())
getActivity().getApplicationContext().unregisterReceiver(downloadReceiver);
}
@Override
public View getNewChildView(final TreeNodeInfo treeNodeInfo) {
final LinearLayout viewLayout;
viewLayout = (LinearLayout) getActivity().getLayoutInflater().inflate(R.layout.download_list_item, null);
return updateView(viewLayout, treeNodeInfo);
}
@Override
public LinearLayout updateView(final View view,
final TreeNodeInfo treeNodeInfo) {
final LinearLayout viewLayout = (LinearLayout) view;
final RemoteFile rfile = treeNodeInfo.getId();
String rfileName = rfile.name;
TextView downloadListItem = (TextView) viewLayout.findViewById(R.id.downloadListItem);
TextView downloadSize = (TextView) viewLayout.findViewById(R.id.downloadSize);
TextView downloadDate = (TextView) viewLayout.findViewById(R.id.downloadDate);
ProgressBar downloadDirProgress = (ProgressBar) viewLayout.findViewById(R.id.downloadDirProgress);
ProgressBar downloadFileProgress = (ProgressBar) view.findViewById(R.id.downloadFileProgress);
ImageView downloadIcon = (ImageView) view.findViewById(R.id.downloadIcon);
ImageButton downloadCancel = (ImageButton) view.findViewById(R.id.downloadCancel);
downloadListItem.setText(rfileName);
if (rfile.isDirectory) {
view.setPadding(8, 8, 8, 8);
downloadSize.setVisibility(View.GONE);
downloadDate.setVisibility(View.GONE);
downloadFileProgress.setVisibility(View.GONE);
downloadIcon.setVisibility(View.GONE);
downloadCancel.setVisibility(View.GONE);
if (listTasks.containsValue(rfile))
downloadDirProgress.setVisibility(View.VISIBLE);
else
downloadDirProgress.setVisibility(View.INVISIBLE);
} else {
view.setPadding(8, 8, 8, 0);
downloadSize.setText(rfile.getFriendlySize());
downloadDate.setText(df.format(new Date(rfile.timestamp)));
downloadSize.setVisibility(View.VISIBLE);
downloadDate.setVisibility(View.VISIBLE);
downloadDirProgress.setVisibility(View.GONE);
if (downloadsByUri.containsKey(rfile.getUri())) {
final DownloadInfo info = downloadsByUri.get(rfile.getUri());
downloadFileProgress.setVisibility(View.VISIBLE);
downloadFileProgress.setMax((int) (rfile.size / 1024));
downloadFileProgress.setProgress(downloadsByUri.get(rfile.getUri()).progress);
downloadIcon.setVisibility(View.GONE);
downloadCancel.setVisibility(View.VISIBLE);
downloadCancel.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (downloadManager.remove(info.reference) > 0) {
removeDownload(info.reference, false);
}
}
});
} else {
File mapFile = new File(
sharedPreferences.getString(Const.KEY_PREF_MAP_PATH, Const.MAP_PATH_DEFAULT),
rfile.name);
downloadFileProgress.setVisibility(View.INVISIBLE);
downloadCancel.setVisibility(View.GONE);
if (!mapFile.exists())
downloadIcon.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.ic_file_download));
else if (mapFile.lastModified() < rfile.timestamp)
// TODO recheck this condition (granularity of timestamps, botched timezones)
downloadIcon.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.ic_refresh));
else
downloadIcon.setImageDrawable(getActivity().getResources().getDrawable(R.drawable.ic_check));
downloadIcon.setVisibility(View.VISIBLE);
}
}
return viewLayout;
}
@Override
public void handleItemClick(final View view, final Object id) {
final RemoteFile rfile = (RemoteFile) id;
if (rfile.isDirectory) {
if (rfile.children != null) {
// Show directory contents (warn if directory is empty)
if (rfile.children.length > 0)
super.handleItemClick(view, id);
else {
String message = getActivity().getString(R.string.status_folder_empty);
Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
}
} else {
String urlStr = rfile.getUriString();
// Retrieve directory contents from server
RemoteDirListTask task = new RemoteDirListTask(this, rfile);
listTasks.put(task, rfile);
task.execute(urlStr);
ProgressBar downloadDirProgress = (ProgressBar) view.findViewById(R.id.downloadDirProgress);
downloadDirProgress.setVisibility(View.VISIBLE);
}
} else {
// check if a download is already in progress
if (!downloadsByUri.containsValue(rfile.getUri())) {
// Download file
final File mapFile = new File(
sharedPreferences.getString(Const.KEY_PREF_MAP_PATH, Const.MAP_PATH_DEFAULT),
rfile.name);
if (downloadsByFile.containsKey(mapFile)) {
// prevent multiple downloads with same map file name
Toast.makeText(getActivity(), getActivity().getString(R.string.status_already_downloading), Toast.LENGTH_LONG).show();
return;
}
// check if we have a current version
// TODO recheck this condition (granularity of timestamps, botched timezones)
if (mapFile.exists() && (mapFile.lastModified() >= rfile.timestamp)) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setMessage(getActivity().getString(R.string.confirm_download));
builder.setPositiveButton(getActivity().getString(R.string.action_yes), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
startDownload(rfile, mapFile, view);
}
});
builder.setNegativeButton(getActivity().getString(R.string.action_no), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// NOP
}
});
builder.show();
} else
startDownload(rfile, mapFile, view);
}
}
}
@Override
public long getItemId(final int position) {
return getTreeId(position).hashCode();
}
@Override
public void onRemoteDirListReady(RemoteDirListTask task, RemoteFile[] rfiles) {
RemoteFile parent = listTasks.get(task);
listTasks.remove(task);
if (rfiles == null) {
// TODO
} else if (rfiles.length == 0) {
manager.refresh();
handleItemClick(null, parent);
} else
for (RemoteFile rf : rfiles)
manager.addAfterChild(parent, rf, null);
}
/**
* Called when a download has completed, failed or been canceled.
*
* This removes the download from all internal data structures and cleans up backup copies, if any: If
* the download was successful, the backup file is deleted. If the download failed or was canceled, the
* incompletely downloaded file is deleted and the backup file is moved to its original location. Finally
* a refresh of the UI is triggered to reflect the new state of the file.
*
* This method is safe to invoke from multiple instances holding identical download lists, as long as the
* calls are made in sequence (which is the case if they are invoked from the main thread's message loop.)
* This is important as race conditions between registering a new receiver and unregistering a previous
* one are possible, though unlikely.
*
* @param reference The reference used by DownloadManager.
* @param success True if the download completed successfully, false if it failed or was canceled.
*/
private void removeDownload(long reference, boolean success) {
DownloadInfo info = downloadsByReference.get(reference);
downloadsByReference.remove(reference);
if (info != null) {
downloadsByUri.remove(info.uri);
downloadsByFile.remove(info.targetFile);
downloadsByFile.remove(info.downloadFile);
// if we're refreshing an existing map file, do the swap operation now
if (success && !info.targetFile.equals(info.downloadFile) && info.downloadFile.exists())
if (!info.targetFile.exists() || info.targetFile.delete())
info.downloadFile.renameTo(info.targetFile);
}
if (downloadsByUri.isEmpty()) {
/*
* All downloads have finished. We no longer need the saved instance state and progress checker,
* and if the activity has been destroyed, we can unregister the broadcast receiver as well.
*/
this.storeInstanceState(null);
stopProgressChecker();
if (isReleased)
getActivity().getApplicationContext().unregisterReceiver(downloadReceiver);
Toast.makeText(getActivity(), getActivity().getString(R.string.status_downloads_completed), Toast.LENGTH_SHORT).show();
}
manager.refresh();
}
/**
* Starts a map download.
*
* This will also start the progress checker.
*
* @param rfile The remote file to download
* @param mapFile The local file to which the map will be saved
* @param view The {@link View} displaying the map file
*/
private void startDownload(RemoteFile rfile, File mapFile, View view) {
Uri uri = rfile.getUri();
Uri destUri = Uri.fromFile(mapFile);
try {
DownloadManager.Request request = new DownloadManager.Request(uri);
//request.setTitle(rfile.name);
//request.setDescription("SatStat map download");
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
//request.setDestinationInExternalFilesDir(getActivity(), dirType, subPath)
request.setDestinationUri(destUri);
Log.d(TAG, String.format("Ready to download %s to %s (local name %s)", uri.toString(), destUri.toString(), mapFile.getName()));
Long reference = downloadManager.enqueue(request);
DownloadInfo info = new DownloadInfo(rfile, uri, mapFile, reference);
downloadsByReference.put(reference, info);
downloadsByUri.put(rfile.getUri(), info);
downloadsByFile.put(mapFile, info);
ProgressBar downloadFileProgress = (ProgressBar) view.findViewById(R.id.downloadFileProgress);
downloadFileProgress.setVisibility(View.VISIBLE);
downloadFileProgress.setMax((int) (rfile.size / 1024));
downloadFileProgress.setProgress(0);
startProgressChecker();
} catch (SecurityException e) {
Log.w(TAG, String.format("Permission not granted to download %s to %s", uri.toString(), destUri.toString()));
}
}
/**
* Starts watching download progress.
*
* This method is safe to call multiple times. Starting an already running progress checker is a no-op.
*/
private void startProgressChecker() {
if (!isProgressCheckerRunning) {
progressChecker.run();
isProgressCheckerRunning = true;
}
}
/**
* Stops watching download progress.
*/
private void stopProgressChecker() {
handler.removeCallbacks(progressChecker);
isProgressCheckerRunning = false;
}
/**
* Stores the state of the associated {@link MapDownloadActivity}.
*
* This is needed to recreate the activity when the download notification is tapped. When that happens,
* the Intent which restarts the activity will have a Bundle extra named {@code KEY_SAVED_INSTANCE_STATE},
* which contains the object stored with this method.
*
* @param savedInstanceState The saved instance state
*/
public void storeInstanceState(Bundle savedInstanceState) {
this.savedInstanceState = savedInstanceState;
}
/**
* Checks download progress and updates status, then re-schedules itself.
*/
private Runnable progressChecker = new Runnable() {
@Override
public void run() {
try {
checkProgress();
manager.refresh();
} finally {
handler.postDelayed(progressChecker, PROGRESS_DELAY);
}
}
};
private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
/*
* This method is safe to invoke from multiple instances holding identical download lists, as long as
* the calls are made in sequence (which is the case if they are invoked from the main thread's
* message loop.) This is important as race conditions between registering a new receiver and
* unregistering a previous one are possible, though unlikely.
*/
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
// this will be called when a download finishes
Long reference = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(reference);
Cursor cursor = downloadManager.query(query);
if (!cursor.moveToFirst()) {
cursor.close();
return;
}
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
//int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON));
cursor.close();
switch (status) {
case DownloadManager.STATUS_SUCCESSFUL:
// The file was downloaded successfully
removeDownload(reference, true);
break;
case DownloadManager.STATUS_FAILED:
// The download failed
removeDownload(reference, false);
break;
case DownloadManager.STATUS_PAUSED:
// The download was paused, update status once more
checkProgress();
manager.refresh();
break;
// The other status values are unusable because they don't fire reliably.
}
} else if (intent.getAction().equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) {
Intent mapDownloadIntent = new Intent(getActivity().getApplicationContext(), MapDownloadActivity.class);
mapDownloadIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
if (savedInstanceState != null)
mapDownloadIntent.putExtra(Const.KEY_SAVED_INSTANCE_STATE, savedInstanceState);
getActivity().getApplicationContext().startActivity(mapDownloadIntent);
} else if (intent.getAction().equals(Const.DOWNLOAD_RECEIVER_REGISTERED)) {
/*
* A new download receiver has been registered. If we're released, unregister.
*/
if (isReleased)
getActivity().getApplicationContext().unregisterReceiver(downloadReceiver);
}
}
};
/**
* Information about a download in progress.
*/
private class DownloadInfo {
/**
* The RemoteFile representing the file being downloaded.
*/
private RemoteFile remoteFile;
/**
* The URI from which the file is actually being downloaded.
*/
private Uri uri;
/**
* The local map file at which the map will be saved once the download finishes.
*/
private File targetFile;
/**
* The file to which the map is being downloaded.
*
* When downloading a map for the first time, this is equal to {@code targetFile}.
* When an existing map is being replaced, this is different from {@code targetFile}.
*/
private File downloadFile;
/**
* The reference under which the download manager tracks the download.
*/
private long reference;
/**
* Download progress in kiB.
*/
private int progress;
private DownloadInfo(RemoteFile remoteFile, Uri uri, File targetFile, long reference) {
super();
this.remoteFile = remoteFile;
this.uri = uri;
this.targetFile = targetFile;
this.downloadFile = targetFile;
this.reference = reference;
this.progress = 0;
}
private DownloadInfo(Uri uri, File targetFile, File downloadFile, long reference, int progress) {
super();
this.uri = uri;
this.targetFile = targetFile;
this.downloadFile = downloadFile;
this.reference = reference;
this.progress = progress;
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/HttpDownloader.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import com.vonglasow.michael.satstat.Const;
import android.text.Html;
import android.text.Spanned;
import android.text.style.URLSpan;
import android.util.Log;
/**
* Provides methods to browse and download from HTTP sites with an FTP-like UI (folder lists).
*/
public class HttpDownloader {
private static final String CONTENT_LENGTH = "content-length";
private static final String TAG = "HttpDownloader";
private static final RemoteFileComparator comparator = new RemoteFileComparator();
/**
* @brief Returns the value of the {@code content-length} header field as a long.
*
* This is a workaround for a limitation of the Android API, which has no native method to
* report content lengths in excess of 2 GB on API levels lower than N.
*
* @param connection The URL connection
*
* @return Content length in bytes, or -1 if an error occurs
*/
private static long getContentLength(URLConnection connection) {
long res = -1;
try {
res = Long.valueOf(connection.getHeaderField(CONTENT_LENGTH));
} catch (Exception e) {
// do nothing and return -1
}
if (res < 0)
res = -1;
return res;
}
/**
* @brief Retrieves information about a remote file or directory
*
* @param url
* @return A {@link RemoteFile} filled in with the data of the remote file or directory, or {@code null}
* if an error occurred.
*/
private static RemoteFile getFileInfo(URL context, String href) {
String baseUrl = context.toString();
boolean isDirectory = false;
String name;
long size;
long timestamp;
URL url;
boolean isTypeKnown = false; // whether we know already if the target is a directory
HttpURLConnection http = null;
/*
Log.d(TAG, String.format("Download from:\n\tProtocol: %s\n\tHost: %s\n\tPort: %d\n\tUser: %s\n\tPath: %s",
url.getProtocol(),
url.getHost(),
url.getPort(),
url.getUserInfo(),
url.getPath()));
*/
try {
url = new URL(context, href);
} catch (MalformedURLException e) {
Log.w(TAG, String.format("%s is not a valid href, skipping", href));
return null;
}
//Log.d(TAG, String.format("Getting information for %s:\n\tURL: %s", href, url.toString()));
if (href.endsWith("/")) {
name = href.substring(0, href.length() - 1);
if (!isTypeKnown)
isDirectory = true; // best guess, hence isTypeKnown remains false
} else
name = href;
try {
http = (HttpURLConnection) url.openConnection();
http.setRequestMethod("HEAD");
http.connect();
if (http.getContentType() != null) {
if (Const.CONTENT_TYPE_HTML.equals(http.getContentType()) || http.getContentType().startsWith(Const.CONTENT_TYPE_HTML + ";")) {
if (!isTypeKnown)
isDirectory = true; // best guess, hence isTypeKnown remains false
} else {
isTypeKnown = true;
isDirectory = false;
}
}
size = getContentLength(http);
timestamp = http.getLastModified();
//Log.d(TAG, String.format("\tContent Type: %s\n\tSize: %d\n\tTimestamp: %d", http.getContentType(), http.getContentLength(), http.getLastModified()));
} catch (IOException e) {
Log.e(TAG, "IOException trying to connect: " + e.getMessage());
e.printStackTrace();
return null;
} finally {
if (http != null)
http.disconnect();
}
return new RemoteFile(baseUrl, isDirectory, name, size, timestamp);
}
/**
* @brief Determines if two URLs have the same port.
*
* This method determines the port used by each of the two URLs (either an explicitly specified
* port or, where absent, the default port) and compares them.
*
* @param url1
* @param url2
* @return true if the URLs effectively use the same port, false otherwise
*/
private static boolean isPortEqual(URL url1, URL url2) {
int port1 = (url1.getPort() > 0) ? url1.getPort() : url1.getDefaultPort();
int port2 = (url2.getPort() > 0) ? url2.getPort() : url2.getDefaultPort();
return (port1 == port2);
}
/**
* @brief Lists a remote directory.
*
* @param urlStr The URL of the remote directory.
* @return An array of {@link RemoteFile} objects representing the contents of the remote directory.
* An empty array is returned if the remote directory is empty. If an error is encountered, {@code null}
* is returned.
*/
public static RemoteFile[] list(String urlStr) {
ArrayList rfiles = new ArrayList();
URL url;
HttpURLConnection http = null;
Spanned parsedHtml;
try {
String base = urlStr;
if (base.charAt(base.length() - 1) != '/')
base = base + "/";
url = new URL(base);
} catch (MalformedURLException e) {
Log.e(TAG, "MalformedURLException: " + e.getMessage());
e.printStackTrace();
return null;
}
/*
Log.d(TAG, String.format("Download from:\n\tProtocol: %s\n\tHost: %s\n\tPort: %d\n\tUser: %s\n\tPath: %s",
url.getProtocol(),
url.getHost(),
url.getPort(),
url.getUserInfo(),
url.getPath()));
*/
try {
http = (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(http.getInputStream());
// FIXME this redirect check will barf on perfectly legit redirects (think Akamai)
if (!url.getHost().equals(http.getURL().getHost()))
throw new IOException("Unexpected redirection! Do you need to sign into your network first?");
if ((http.getContentType() == null) || (!Const.CONTENT_TYPE_HTML.equals(http.getContentType()) && !http.getContentType().startsWith(Const.CONTENT_TYPE_HTML + ";")))
throw new IOException(String.format("Response is not in HTML format, got %s", http.getContentType()));
// read output into a stream which we can convert to a string so we can process it further
ByteArrayOutputStream out = new ByteArrayOutputStream();
int i;
try {
i = in.read();
while (i != -1) {
out.write(i);
i = in.read();
}
in.close();
} catch (IOException e) {
e.printStackTrace();
}
parsedHtml = Html.fromHtml(out.toString());
// links get converted to URLSpan objects, examine them
URLSpan [] us = parsedHtml.getSpans(0, parsedHtml.length(), android.text.style.URLSpan.class);
for (URLSpan u : us) {
String href = u.getURL();
//Log.d(TAG, href);
URL hrefUrl;
try {
hrefUrl = new URL(url, href);
if (!url.getProtocol().matches(hrefUrl.getProtocol())
|| !url.getHost().matches(hrefUrl.getHost())
|| !isPortEqual(url, hrefUrl))
continue;
} catch (MalformedURLException e) {
Log.w(TAG, String.format("%s is not a valid href, skipping", href));
continue;
}
// both URLs refer to the same protocol, host and port, therefore we can discard those parts
href = hrefUrl.getPath();
// href is now just a path (absolute or relative)
// get the base path (path from url, ensuring it ends with a slash)
String basePath = url.getPath();
if (href.startsWith(basePath))
href = href.substring(basePath.length());
else if (href.startsWith("/"))
continue;
// href is now a relative path but may still contain queries or anchors
// query and ref are null if not specified (TODO what if they are specified but empty?)
//Log.d(TAG, String.format("\tquery: %s anchor: %s", hrefUrl.getQuery(), hrefUrl.getRef()));
if ((hrefUrl.getQuery() != null) && (!hrefUrl.getQuery().isEmpty()))
continue;
if ((hrefUrl.getRef() != null) && (!hrefUrl.getRef().isEmpty()))
continue;
// href is now a relative path, free of queries or anchors but any number of levels deep
if (href.substring(0, href.length() - 1).indexOf("/") >= 0)
continue;
// href points to an immediate child object, examine it
RemoteFile rf = getFileInfo(url, href);
if (rf == null)
continue;
rfiles.add(rf);
}
Collections.sort(rfiles, comparator);
return rfiles.toArray(new RemoteFile[]{});
} catch (IOException e) {
Log.e(TAG, "IOException trying to connect: " + e.getMessage());
e.printStackTrace();
return null;
} finally {
if (http != null)
http.disconnect();
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/PermissionHelper.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
import com.vonglasow.michael.satstat.Const;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback;
import android.support.v4.os.ResultReceiver;
import android.support.v7.app.AppCompatActivity;
/**
* Provides helper methods to request permissions from components other than Activities.
*/
public class PermissionHelper {
private static final String TAG = PermissionHelper.class.getSimpleName();
/**
* Requests permissions to be granted to this application.
*
* This method is a wrapper around
* {@link android.support.v4.app.ActivityCompat#requestPermissions(android.app.Activity, String[], int)}
* which works in a similar way, except it can be called from non-activity contexts. When called, it
* displays a notification with a customizable title and text. When the user taps the notification, an
* activity is launched in which the user is prompted to allow or deny the request.
*
* After the user has made a choice,
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}
* is called, reporting whether the permissions were granted or not.
*
* @param context The context from which the request was made. The context supplied must implement
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback} and will receive the
* result of the operation.
* @param permissions The requested permissions
* @param requestCode Application specific request code to match with a result reported to
* {@link android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(int, String[], int[])}
* @param notificationTitle The title for the notification
* @param notificationText The text for the notification
* @param notificationIcon Resource identifier for the notification icon
*/
public static void requestPermissions(final T context, String[] permissions, int requestCode, String notificationTitle, String notificationText, int notificationIcon) {
ResultReceiver resultReceiver = new ResultReceiver(new Handler(Looper.getMainLooper())) {
@Override
protected void onReceiveResult (int resultCode, Bundle resultData) {
String[] outPermissions = resultData.getStringArray(Const.KEY_PERMISSIONS);
int[] grantResults = resultData.getIntArray(Const.KEY_GRANT_RESULTS);
context.onRequestPermissionsResult(resultCode, outPermissions, grantResults);
}
};
Intent permIntent = new Intent(context, PermissionRequestActivity.class);
permIntent.putExtra(Const.KEY_RESULT_RECEIVER, resultReceiver);
permIntent.putExtra(Const.KEY_PERMISSIONS, permissions);
permIntent.putExtra(Const.KEY_REQUEST_CODE, requestCode);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(permIntent);
PendingIntent permPendingIntent =
stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setSmallIcon(notificationIcon)
.setContentTitle(notificationTitle)
.setContentText(notificationText)
.setOngoing(true)
//.setCategory(Notification.CATEGORY_STATUS)
.setAutoCancel(true)
.setWhen(0)
.setContentIntent(permPendingIntent)
.setStyle(null);
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(requestCode, builder.build());
}
/**
* A blank {@link Activity} on top of which permission request dialogs can be displayed
*/
public static class PermissionRequestActivity extends AppCompatActivity {
ResultReceiver resultReceiver;
String[] permissions;
int requestCode;
/**
* Called when the user has made a choice in the permission dialog.
*
* This method wraps the responses in a {@link Bundle} and passes it to the {@link ResultReceiver}
* specified in the {@link Intent} that started the activity, then closes the activity.
*/
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
Bundle resultData = new Bundle();
resultData.putStringArray(Const.KEY_PERMISSIONS, permissions);
resultData.putIntArray(Const.KEY_GRANT_RESULTS, grantResults);
resultReceiver.send(requestCode, resultData);
finish();
}
/**
* Called when the activity is started.
*
* This method obtains several extras from the {@link Intent} that started the activity: the request
* code, the requested permissions and the {@link ResultReceiver} which will receive the results.
* After that, it issues the permission request.
*/
@Override
protected void onStart() {
super.onStart();
resultReceiver = this.getIntent().getParcelableExtra(Const.KEY_RESULT_RECEIVER);
permissions = this.getIntent().getStringArrayExtra(Const.KEY_PERMISSIONS);
requestCode = this.getIntent().getIntExtra(Const.KEY_REQUEST_CODE, 0);
ActivityCompat.requestPermissions(this, permissions, requestCode);
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/RemoteDirListListener.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
/**
* Receives notifications when a remote directory listing has completed.
*/
public interface RemoteDirListListener {
/**
* Called when a remote directory listing has been retrieved.
*
* @param rfiles An array of all objects in the remote directory.
*/
public void onRemoteDirListReady(RemoteDirListTask task, RemoteFile[] rfiles);
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/RemoteDirListTask.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.TimeZone;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
/**
* A task which retrieves the contents of a remote directory in the background and notifies a listener
* upon completion.
*/
public class RemoteDirListTask extends AsyncTask {
private static final String TAG = RemoteDirListTask.class.getSimpleName();
private RemoteDirListListener listener = null;
private RemoteFile parent = null;
/**
* Creates a new {@code RemoteDirListTask} task, and registers it with a listener.
*
* @param listener The {@link RemoteDirListListener} which will be notified when the task has completed.
* @param parent The directory to be listed. When this task finishes, it populates the {@code children}
* member of {@code parent} with the objects it retrieved. May be {@code null}.
*/
public RemoteDirListTask(RemoteDirListListener listener, RemoteFile parent) {
super();
this.listener = listener;
this.parent = parent;
}
@Override
protected RemoteFile[] doInBackground(String... params) {
Uri uri = Uri.parse(params[0]);
RemoteFile[] rfiles = null;
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ROOT);
df.setTimeZone(TimeZone.getDefault());
if (uri.getScheme() == null)
return null;
if (uri.getScheme().equals("http") || uri.getScheme().equals("https"))
rfiles = HttpDownloader.list(params[0]);
if (rfiles == null)
Log.w(TAG, "Error – could not retrieve content!");
else if (rfiles.length == 0)
Log.w(TAG, "Remote directory is empty.");
/*
else {
Log.d(TAG, "Remote directory contents:");
for (RemoteFile rf : rfiles)
Log.d(TAG, String.format("\n\t%s \t%s \t%s \t%s",
rf.isDirectory ? "D" : "F",
df.format(new Date(rf.timestamp)),
rf.getFriendlySize(),
rf.name));
}
*/
return rfiles;
}
protected void onPostExecute(RemoteFile[] result) {
if (parent != null)
parent.children = result;
if (listener != null)
listener.onRemoteDirListReady(this, result);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/RemoteFile.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
import java.io.Serializable;
import java.util.Locale;
import android.net.Uri;
/**
* Describes a file system object that can be fetched from a remote (HTTP or FTP) server.
*/
// Implementing Serializable is required for saving DownloadTreeStateManager to a Bundle
public class RemoteFile implements Serializable {
private static final long serialVersionUID = 1L;
/**
* The URL of the containing folder.
*/
public String baseUrl;
/**
* The children of this object, i.e. files in this folder and direct subfolders. Valid for folders only.
* A value of {@code null} indicates that a folder listing has not yet been retrieved, whereas an empty
* array indicates a folder that is known to be empty.
*/
public RemoteFile[] children = null;
/**
* Whether the file system object is a directory or a regular file.
*/
public boolean isDirectory;
/**
* The local name of the file system object.
*/
public String name;
/**
* The size of the file system object, in bytes (-1 if unknown).
*/
public long size;
/**
* The timestamp of the file system object. This is typically the last modification time. 0 if unknown.
*/
public long timestamp;
public RemoteFile(String baseUrl, boolean isDirectory, String name, long size, long timestamp) {
super();
this.baseUrl = baseUrl;
this.isDirectory = isDirectory;
this.name = name;
this.size = size;
this.timestamp = timestamp;
}
public String getFriendlySize() {
if (size < 1024)
return String.format(Locale.getDefault(), "%d", size);
float tmp = size / 1024;
if (tmp < 1024)
return String.format(Locale.getDefault(), "%.1fk", tmp);
tmp /= 1024;
if (tmp < 1024)
return String.format(Locale.getDefault(), "%.1fM", tmp);
tmp /= 1024;
if (tmp < 1024)
return String.format(Locale.getDefault(), "%.1fG", tmp);
tmp /= 1024;
return String.format(Locale.getDefault(), "%.1fT", tmp);
}
/**
* Returns the full URI to the remote file.
*/
public Uri getUri() {
Uri baseUri = Uri.parse(this.baseUrl);
Uri uri = baseUri.buildUpon().appendPath(this.name).build();
return uri;
}
/**
* Returns the full URI to the remote file as a string.
*/
public String getUriString() {
return this.getUri().toString();
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/RemoteFileComparator.java
================================================
package com.vonglasow.michael.satstat.utils;
import java.text.Collator;
import java.util.Comparator;
/**
* A Comparator for {@link RemoteFile}s.
*
* Sorting is done by name, which may alternate between files and directories (unlike the customary file
* manager experience, where directories tend to be listed first). Sort order is determined by the default
* locale.
*/
public class RemoteFileComparator implements Comparator {
@Override
public int compare(RemoteFile lhs, RemoteFile rhs) {
Collator collator = Collator.getInstance();
return collator.compare(lhs.name, rhs.name);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/WifiCapabilities.java
================================================
/*
* Copyright © 2014–2016 Michael von Glasow.
* Portions copyright © 2007, 2012 The Android Open Source Project
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.utils;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import android.net.wifi.ScanResult;
import android.util.Log;
public abstract class WifiCapabilities {
// Constants used for different security types
public static final String PSK = "PSK";
public static final String WEP = "WEP";
public static final String EAP = "EAP";
public static final String OPEN = "Open";
public static final String[] EAP_METHOD = { "PEAP", "TLS", "TTLS" };
/** String present in capabilities if the scan result is ad-hoc */
private static final String ADHOC_CAPABILITY = "[IBSS]";
/** String present in capabilities if the scan result is enterprise secured */
private static final String ENTERPRISE_CAPABILITY = "-EAP-";
public static final String BSSID_ANY = "any";
public static final int NETWORK_ID_NOT_SET = -1;
/** This should be used with care! */
static final int NETWORK_ID_ANY = -2;
public static final int MATCH_NONE = 0;
public static final int MATCH_WEAK = 1;
public static final int MATCH_STRONG = 2;
public static final int MATCH_EXACT = 3;
/* Enterprise Fields */
public static final int IDENTITY = 0;
public static final int ANONYMOUS_IDENTITY = 1;
public static final int CLIENT_CERT = 2;
public static final int CA_CERT = 3;
public static final int PRIVATE_KEY = 4;
public static final int MAX_ENTRPRISE_FIELD = 5;
public static final String CAPTIVE_PORTAL_SERVER = "clients3.google.com";
private static final int SOCKET_TIMEOUT_MS = 10000;
public static final int NETWORK_AVAILABLE = 0;
public static final int NETWORK_CAPTIVE_PORTAL = 1;
public static final int NETWORK_ERROR = 2;
/**
* @return The security of a given {@link ScanResult}.
*/
public static String getScanResultSecurity(ScanResult scanResult) {
final String cap = scanResult.capabilities;
final String[] securityModes = { WEP, PSK, EAP };
for (int i = securityModes.length - 1; i >= 0; i--) {
if (cap.contains(securityModes[i])) {
return securityModes[i];
}
}
return OPEN;
}
/**
* @return Whether the given ScanResult represents an adhoc network.
*/
public static boolean isAdhoc(ScanResult scanResult) {
return scanResult.capabilities.contains(ADHOC_CAPABILITY);
}
/**
* @return Whether the given ScanResult has enterprise security.
*/
public static boolean isEnterprise(ScanResult scanResult) {
return scanResult.capabilities.contains(ENTERPRISE_CAPABILITY);
}
/**
* Checks if an unrestricted Internet connection is available.
*
* This method detects captive portals (also known as walled gardens),
* which redirect Web traffic to a sign-in page as long as the user has not
* provided any credentials. It does so by connecting to a particular
* Web address, which will respond with HTTP status code 204 (no content)
* and an empty result body. If a different response (such as a document
* or redirection) is obtained, it will assume the presence of a captive
* portal.
*
* Once the user has signed into a captive portal, it will not be reported
* as such, provided the portal grants transparent Internet access to
* signed-in users.
*
* Since this method involves a network operation, it cannot be called from
* the main UI thread. Consider instead creating an {@link AsyncTask}
* around it.
* @return NETWORK_AVAILABLE if we have unrestricted network access,
* NETWORK_CAPTIVE_PORTAL if we are behind a captive portal or
* NETWORK_ERROR if a network error occurred during the check, which
* happens if no network is available.
*/
public static int getNetworkConnectivity() {
HttpURLConnection urlConnection = null;
String mUrl = "http://" + CAPTIVE_PORTAL_SERVER + "/generate_204";
Log.d(WifiCapabilities.class.getSimpleName(), "Checking " + mUrl + " to see if we're behind a captive portal");
try {
URL url = new URL(mUrl);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setInstanceFollowRedirects(false);
urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
urlConnection.setUseCaches(false);
urlConnection.getInputStream();
// we got a valid response, but not from the real google
return (urlConnection.getResponseCode() != 204)?NETWORK_CAPTIVE_PORTAL:NETWORK_AVAILABLE;
} catch (IOException e) {
Log.d(WifiCapabilities.class.getSimpleName(), "Probably not a portal: exception " + e);
return NETWORK_ERROR;
} finally {
if (urlConnection != null) {
urlConnection.disconnect();
}
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/utils/WifiScanResultComparator.java
================================================
package com.vonglasow.michael.satstat.utils;
import java.util.Comparator;
import android.net.wifi.ScanResult;
/**
* A comparator for {@link android.net.wifi.ScanResult}.
*
* The criterion by which this comparator will perform comparison can be altered at runtime.
* However, if two values are equal by that criterion, the {@code bssid} member will always be used
* as a secondary criterion. Values will be reported as being equal only if they are equal by both
* criteria. The initial criterion is {@link #WIFI_SORT_BSSID}.
*/
public class WifiScanResultComparator implements Comparator {
/**
* Sort WiFis by BSSID, ascending order
*/
public static final int WIFI_SORT_BSSID = 0;
/**
* Sort WiFis by name (SSID), alphabetically and case-insensitive
*/
public static final int WIFI_SORT_SSID = 1;
/**
* Sort WiFis by channel (frequency), ascending
*/
public static final int WIFI_SORT_FREQUENCY = 2;
/**
* Sort WiFis by signal strength (level), strongest first
*/
public static final int WIFI_SORT_LEVEL = 3;
// The criterion to use for comparison, initially set to BSSID (fallback)
private int criterion = WIFI_SORT_BSSID;
/**
* Compares two ScanResults.
*
* Comparison is performed using the previously selected criterion. The BSSID is used as a secondary
* criterion if both {@code lhs} and {code lhs} are equal by the chosen criterion.
*
* @return A negative value if {@code lhs < rhs}, a positive value if {@code lhs > rhs}, or zero if
* both are equal.
*/
@Override
public int compare(ScanResult lhs, ScanResult rhs) {
switch(criterion) {
case WIFI_SORT_SSID:
int temp = lhs.SSID.compareToIgnoreCase(rhs.SSID);
if (temp != 0)
return temp;
break;
case WIFI_SORT_FREQUENCY:
if (lhs.frequency < rhs.frequency)
return -1;
else if (lhs.frequency > rhs.frequency)
return 1;
break;
case WIFI_SORT_LEVEL:
if (lhs.level > rhs.level)
return -1;
else if (lhs.level < rhs.level)
return 1;
break;
}
return lhs.BSSID.compareToIgnoreCase(rhs.BSSID);
}
/**
* Sets the criterion for comparison.
*
* If two values are equal by that criterion, the {@code bssid} member will always be used as a
* secondary criterion. Values will be reported as being equal only if they are equal by both
* criteria.
*
* @param newCriterion
*/
public void setCriterion(int newCriterion) {
criterion = newCriterion;
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/widgets/GpsSnrView.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.widgets;
import com.vonglasow.michael.satstat.R;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.location.GpsSatellite;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
/**
* Displays the signal-to-noise ratio of the GPS satellites in a bar chart.
*/
public class GpsSnrView extends View {
private final String TAG = "GpsSnrView";
/**
* The highest currently supported NMEA ID.
*/
private final int MAX_NMEA_ID = 336;
private Iterable mSats;
private Paint activePaint;
private Paint inactivePaint;
private Paint gridPaint;
private Paint gridPaintStrong;
private Paint gridPaintNone;
private Paint labelPaint;
// Stroke width for grid lines
private int gridStrokeWidth;
// Display density
private float density;
// Effective height of label text in pixels
private int textHeight;
// Preferred height of the view in pixels so that labels and legible bars can be accommodated
private int preferredHeight;
/*
* Which satellites to draw:
* 1–32: GPS
* 33–54: Various SBAS systems (EGNOS, WAAS, SDCM, GAGAN, MSAS) – some IDs still unused
* 55–64: not used (might be assigned to further SBAS systems)
* 65–88: GLONASS
* 89–96: GLONASS (future extensions?)
* 97–192: not used
* 193–195: QZSS
* 196–200: QZSS (future extensions?)
* 201–235: Beidou
* 236–300: not used
* 301–336: Galileo
*/
private boolean draw_1_32 = false;
private boolean draw_33_54 = false;
private boolean draw_55_64 = false;
private boolean draw_65_88 = false;
private boolean draw_89_96 = false;
private boolean draw_97_192 = false;
private boolean draw_193_195 = false;
private boolean draw_196_200 = false;
private boolean draw_201_235 = false;
private boolean draw_236_300 = false;
private boolean draw_301_336 = false;
/**
* @param context
*/
public GpsSnrView(Context context) {
super(context);
doInit(context);
}
/**
* @param context
* @param attrs
*/
public GpsSnrView(Context context, AttributeSet attrs) {
super(context, attrs);
doInit(context);
}
/**
* @param context
* @param attrs
* @param defStyle
*/
public GpsSnrView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
doInit(context);
}
private void doInit(Context context) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
density = metrics.density;
gridStrokeWidth = Math.max(1, (int) (density));
activePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
activePaint.setColor(Color.parseColor("#FF80CBC4")); // Teal 200
activePaint.setStyle(Paint.Style.FILL);
inactivePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
inactivePaint.setColor(Color.parseColor("#FFF44336")); // Red 500
inactivePaint.setStyle(Paint.Style.FILL);
gridPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
gridPaint.setColor(Color.parseColor("#FF424242")); // Gray 800
gridPaint.setStyle(Paint.Style.STROKE);
gridPaint.setStrokeWidth(gridStrokeWidth);
gridPaintStrong = new Paint(gridPaint);
gridPaintStrong.setColor(Color.parseColor("#FFFFFFFF"));
gridPaintNone = new Paint(gridPaint);
gridPaintNone.setColor(Color.parseColor("#00000000"));
// FIXME style text properly
labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
labelPaint.setStyle(Paint.Style.FILL);
labelPaint.setTextAlign(Paint.Align.CENTER);
labelPaint.setColor(context.getResources().getColor(R.color.secondary_text_default_material_dark));
labelPaint.setTextSize(context.getResources().getDimensionPixelSize(R.dimen.abc_text_size_small_material));
/*
int ap = R.style.TextAppearance_AppCompat_Medium;
TypedArray appearance = null;
appearance = context.getTheme().obtainStyledAttributes(ap, R.styleable.AppCompatTextView);
if (appearance != null) {
int n = appearance.getIndexCount();
for (int i = 0; i < n; i++) {
int attr = appearance.getIndex(i);
switch (attr) {
case R.styleable.TextAppearance_android_textColor:
labelPaint.setColor(appearance.getColor(attr, labelPaint.getColor()));
break;
case R.styleable.TextAppearance_android_textSize:
//labelPaint.setTextSize(appearance.getDimensionPixelSize(attr, (int) labelPaint.getTextSize()));
break;
case R.styleable.TextAppearance_android_typeface:
//labelPaint.setTypeface(); // typefaceIndex = appearance.getInt(attr, -1);
break;
case R.styleable.TextAppearance_android_shadowColor:
case R.styleable.TextAppearance_android_shadowDx:
case R.styleable.TextAppearance_android_shadowDy:
case R.styleable.TextAppearance_android_shadowRadius:
// not yet implemented
break;
}
}
appearance.recycle();
}
*/
/*
* Get the total height of the text. Note that this is not the same as getTextSize/setTextSize.
* Also note that the ascent is negative and descent is positive, hence descent - ascent will give us
* absolute text height (a positive number).
*/
textHeight = (int) Math.ceil(labelPaint.descent() - labelPaint.ascent());
/*
* Height should be the same as two rows of small text plus a row of medium text. This is a
* rough approximation based on text sizes and the ratio between text size and actual height.
*/
preferredHeight = (int) (
(2 * labelPaint.getTextSize() + context.getResources().getDimensionPixelSize(R.dimen.abc_text_size_medium_material))
* textHeight / labelPaint.getTextSize()
);
}
/**
* Draws the grid lines and labels.
*/
private void drawGrid(Canvas canvas) {
//don't use Canvas.getWidth() and Canvas.getHeight() here, they may return incorrect values
int w = getWidth();
int h = getHeight();
// left boundary
canvas.drawLine((float) gridStrokeWidth / 2, 0,
(float) gridStrokeWidth / 2, h - textHeight, gridPaintStrong);
int numBars = getNumBars();
if (draw_1_32)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_001_032), 1, 32, numBars);
if (draw_33_54)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_033_054), 33, 22, numBars);
if (draw_55_64)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_055_064), 55, 10, numBars);
// 65–88 is GLONASS, 89–96 is for possible future GLONASS extensions
if (draw_65_88 && draw_89_96)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_065_088), 65, 32, numBars);
else if (draw_65_88)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_065_088), 65, 24, numBars);
else if (draw_89_96)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_065_088), 89, 8, numBars);
if (draw_97_192)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_097_192), 97, 96, numBars);
// 193–195 is QZSS, 196–200 is for possible future QZSS extensions
if (draw_193_195 && draw_196_200)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_193_195), 193, 8, numBars);
else if (draw_193_195)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_193_195), 193, 3, numBars);
else if (draw_196_200)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_193_195), 196, 5, numBars);
if (draw_201_235)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_201_235), 201, 35, numBars);
// 236–300 (currently unused)
if (draw_236_300)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_236_300), 236, 65, numBars);
// 301–336 is Galileo
if (draw_301_336)
drawLabel(canvas, getContext().getResources().getString(R.string.title_nmea_301_336), 301, 36, numBars);
// range boundaries and auxiliary lines (after every 4th satellite)
for (int nmeaID = 1; nmeaID < MAX_NMEA_ID; nmeaID++) {
int pos = getGridPos(nmeaID);
if (pos > 0) {
float x = (float) gridStrokeWidth / 2
+ pos * (w - gridStrokeWidth) / numBars;
Paint paint = gridPaintNone;
switch(nmeaID) {
case 32:
case 64:
case 96:
case 192:
case 200:
case 235:
case 300:
case 336:
paint = gridPaintStrong;
break;
case 54:
if (!draw_55_64)
paint = gridPaintStrong;
break;
case 88:
if (!draw_89_96)
paint = gridPaintStrong;
else
paint = gridPaint;
break;
case 195:
if (!draw_196_200)
paint = gridPaintStrong;
default:
if ((nmeaID % 4) == 0)
paint = gridPaint;
break;
}
canvas.drawLine(x, 0, x, h - textHeight, paint);
}
}
// right boundary
canvas.drawLine(w - (float) gridStrokeWidth / 2, h - textHeight,
w - (float) gridStrokeWidth / 2, 0, gridPaintStrong);
// bottom line
canvas.drawLine(0, h - textHeight - (float) gridStrokeWidth / 2,
w, h - textHeight - (float) gridStrokeWidth / 2, gridPaintStrong);
}
/**
* Draws the label for a satellite range.
*
* @param canvas The {@code Canvas} on which the SNR view will appear.
* @param label The text to be displayed (the description of the satellite range, such as "GPS", "GLONASS" or "Beidou")
* @param startBar The NMEA ID of the first satellite in the range
* @param rangeBars The number of NMEA IDs in the range (ranges must be contiguous)
* @param numBars Total number of SNR bars being displayed, as returned by getNumBars()
*/
private void drawLabel(Canvas canvas, String label, int startBar, int rangeBars, int numBars) {
int offsetBars = getGridPos(startBar) - 1;
int w = getWidth();
int h = getHeight();
Path labelPath = new Path();
labelPath.reset();
labelPath.moveTo(gridStrokeWidth + offsetBars * (w - gridStrokeWidth) / numBars, h);
labelPath.rLineTo(rangeBars * (w - gridStrokeWidth) / numBars - gridStrokeWidth, 0);
canvas.drawTextOnPath(label, labelPath, 0, -labelPaint.descent(), labelPaint);
}
/**
* Draws the SNR bar for a satellite.
*
* @param canvas The {@code Canvas} on which the SNR view will appear.
* @param nmeaID The NMEA ID of the satellite, as returned by {@link android.location.GpsSatellite#getPrn()}.
* @param snr The signal-to-noise ratio (SNR) for the satellite.
* @param used Whether the satellite is used in the fix.
*/
private void drawSat(Canvas canvas, int nmeaID, float snr, boolean used) {
int w = getWidth();
int h = getHeight() - textHeight;
int i = getGridPos(nmeaID);
int x0 = (i - 1) * (w - gridStrokeWidth) / getNumBars() + gridStrokeWidth / 2;
int x1 = i * (w - gridStrokeWidth) / getNumBars() - gridStrokeWidth / 2;
int y0 = h - gridStrokeWidth;
int y1 = (int) (y0 * (1 - Math.min(snr, 60) / 60));
canvas.drawRect(x0, y1, x1, h, used?activePaint:inactivePaint);
}
/**
* Returns the position of the SNR bar for a satellite in the grid.
*
* This function returns the position at which the SNR bar for the
* satellite with the given {@code nmeaID} will appear in the grid, taking
* into account the visibility of NMEA ID ranges.
*
* @param nmeaID The NMEA ID of the satellite, as returned by {@link android.location.GpsSatellite#getPrn()}.
* @return The position of the SNR bar in the grid. The position of the first visible bar is 1. If {@code nmeaID} falls within a hidden range, -1 is returned.
*/
private int getGridPos(int nmeaID) {
if (nmeaID < 1) return -1;
int skip = 0;
if (nmeaID > 32) {
if (!draw_1_32) skip+=32;
if (nmeaID > 54) {
if (!draw_33_54) skip+=22;
if (nmeaID > 64) {
if (!draw_55_64) skip+=10;
if (nmeaID > 88) {
if (!draw_65_88) skip+=24;
if (nmeaID > 96) {
if (!draw_89_96) skip+=8;
if (nmeaID > 192) {
if (!draw_97_192) skip+=96;
if (nmeaID > 195) {
if (!draw_193_195) skip+=3;
if (nmeaID > 200) {
if (!draw_196_200) skip+=5;
if (nmeaID > 235) {
if (!draw_201_235) skip+=35;
if (nmeaID > 300) {
if (nmeaID > MAX_NMEA_ID) return -1;
else if (!draw_301_336) return -1;
else if (!draw_236_300) skip+=65;
} else {
// 235 < nmeaID <= 300
if (!draw_236_300) return -1;
}
} else {
// 200 < nmeaID <= 235
if (!draw_201_235) return -1;
}
} else {
// 195 < nmeaID <= 200
if (!draw_196_200) return -1;
}
} else {
// 192 < nmeaID <= 195
if (!draw_193_195) return -1;
}
} else {
// 96 < nmeaID <= 192
if (!draw_97_192) return -1;
}
} else {
// 88 < nmeaID <= 96
if (!draw_89_96) return -1;
}
} else {
// 64 < nmeaID <= 88
if (!draw_65_88) return -1;
}
} else {
// 54 < nmeaID <= 64
if (!draw_55_64) return -1;
}
} else {
// 32 < nmeaID <= 54
if (!draw_33_54) return -1;
}
} else {
// nmeaID <= 32
if (!draw_1_32) return -1;
}
return nmeaID - skip;
}
/**
* Returns the number of SNR bars to draw
*
* The number of bars to draw varies depending on the systems supported by the device. Common
* numbers are 32 for a GPS-only receiver, 56 for a combined GPS/GLONASS receiver or 91 for a
* combined GPS/GLONASS/Beidou receiver. Another 36 bars are needed for Galileo; some receivers
* require additional bars for regional GNSS or assistance systems.
*
* @return The number of bars to draw
*/
private int getNumBars() {
return (draw_1_32 ? 32 : 0)
+ (draw_33_54 ? 22 : 0)
+ (draw_55_64 ? 10 : 0)
+ (draw_65_88 ? 24 : 0)
+ (draw_89_96 ? 8 : 0)
+ (draw_97_192 ? 96 : 0)
+ (draw_193_195 ? 3 : 0)
+ (draw_196_200 ? 5 : 0)
+ (draw_201_235 ? 35 : 0)
+ (draw_236_300 ? 65 : 0)
+ (draw_301_336 ? 36 : 0);
}
/**
* Initializes the SNR grid.
*
* This method iterates through {@link #mSats} to determine which ranges of
* NMEA IDs will be drawn.
*/
protected void initializeGrid() {
// iterate through list to find out how many bars to draw
if (mSats != null)
for (GpsSatellite sat : mSats) {
int prn = sat.getPrn();
if (prn < 1) {
Log.wtf(TAG, String.format("Got satellite with invalid NMEA ID %d", prn));
} else if (prn <= 32) {
draw_1_32 = true;
} else if (prn <= 54) {
draw_33_54 = true;
} else if (prn <= 64) {
// most likely an extended SBAS range, display the lower range, too
draw_33_54 = true;
draw_55_64 = true;
} else if (prn <= 88) {
draw_65_88 = true;
} else if (prn <= 96) {
// most likely an extended GLONASS range, display the lower range, too
draw_65_88 = true;
draw_89_96 = true;
} else if (prn <= 192) {
draw_97_192 = true; // TODO: do we really want to enable this huge 96-sat block?
Log.w(TAG, String.format("Got satellite with NMEA ID %d (from the huge unassigned 97-192 range)", prn));
} else if (prn <= 195) {
draw_193_195 = true;
} else if (prn <= 200) {
// most likely an extended QZSS range, display the lower range, too
draw_193_195 = true;
draw_196_200 = true;
} else if (prn <= 235) {
draw_201_235 = true;
} else if (prn <= 300) {
draw_236_300 = true; // TODO: same as above, do we really want to enable this?
} else if (prn <= 336) {
draw_301_336 = true;
} else {
Log.w(TAG, String.format("Got satellite with NMEA ID %d, possibly unsupported system", prn));
}
}
/*
* If we didn't get any valid ranges, display at least the GPS range.
* No need to check for extended ranges here - if they get drawn, so
* will their corresponding base range.
*/
if (!(draw_1_32 || draw_33_54 || draw_65_88 || draw_97_192 || draw_193_195 || draw_201_235
|| draw_236_300 || draw_301_336))
draw_1_32 = true;
}
/**
* Redraws the SNR view.
*
* This method is called whenever the view needs to be redrawn. Besides the
* usual cases of view creation/recreation, this also occurs when the
* {@link #showSats(Iterable)} has been called to indicate new SNR data is
* available.
*/
@Override
protected void onDraw(Canvas canvas) {
initializeGrid();
// draw the SNR bars
if (mSats != null)
for (GpsSatellite sat : mSats)
drawSat(canvas, sat.getPrn(), sat.getSnr(), sat.usedInFix());
// draw the grid on top
drawGrid(canvas);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), preferredHeight);
}
/**
* Refreshes the SNR view with current data.
*
* Call this method when new SNR data is available. It will update the SNR
* view's internal list of {@code GpsSatellite}s and trigger a redraw.
*
* @param sats A list of satellites currently in view.
*/
public void showSats(Iterable sats) {
mSats = sats;
invalidate();
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/widgets/GpsStatusView.java
================================================
/*
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.widgets;
import com.vonglasow.michael.satstat.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.location.GpsSatellite;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
public class GpsStatusView extends SquareView {
private float mYaw = 0;
private float mRotation = 0;
private int mW = 0;
private int mH = 0;
private Iterable mSats;
private Paint activePaint;
private Paint inactivePaint;
private Paint northPaint;
private Paint gridPaint;
private Paint gridBorderPaint;
private Paint labelPaint;
private Path northArrow = new Path();
private Path labelPathN = new Path();
private Path labelPathE = new Path();
private Path labelPathS = new Path();
private Path labelPathW = new Path();
private int gridStrokeWidth;
private float snrScale;
private float density;
// Compensation for display rotation. Use Surface.ROTATION_* as index (0, 90, 180, 270 deg).
@SuppressWarnings("boxing")
private final static Integer zeroYaw[] = {0, 90, 180, 270};
public GpsStatusView(Context context) {
super(context);
doInit(context);
}
public GpsStatusView(Context context, AttributeSet attrs) {
super(context, attrs);
doInit(context);
}
public GpsStatusView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
doInit(context);
}
private void doInit(Context context) {
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
density = metrics.density;
snrScale = 0.2f * density;
gridStrokeWidth = Math.max(1, (int) (density));
activePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
activePaint.setColor(Color.parseColor("#FF80CBC4")); // Teal 200
activePaint.setStyle(Paint.Style.FILL);
inactivePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
inactivePaint.setColor(Color.parseColor("#FFF44336")); // Red 500
inactivePaint.setStyle(Paint.Style.FILL);
gridPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
gridPaint.setColor(Color.parseColor("#FFFF9800")); // Orange 500
gridPaint.setStyle(Paint.Style.STROKE);
gridPaint.setStrokeWidth(gridStrokeWidth);
gridBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
gridBorderPaint.setColor(Color.parseColor("#50FF9800")); // Orange 500 @ 30%
gridBorderPaint.setStyle(Paint.Style.STROKE);
northPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
northPaint.setColor(Color.parseColor("#FFF44336")); // Red 500
northPaint.setStyle(Paint.Style.FILL);
labelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
labelPaint.setColor(Color.parseColor("#FFFF9800")); // Orange 500
labelPaint.setStyle(Paint.Style.FILL);
labelPaint.setTextAlign(Paint.Align.CENTER);
}
/*
* Draws a satellite in the sky grid.
*/
private void drawSat(Canvas canvas, int prn, float azimuth, float elevation, float snr, boolean used) {
float r = (90 - elevation) * mW * 0.9f / 200;
float x = (float) (r * Math.sin(azimuth * Math.PI / 180));
float y = (float) -(r * Math.cos(azimuth * Math.PI / 180));
canvas.drawCircle(x, y, snr * snrScale, used?activePaint:inactivePaint);
}
@Override
protected void onDraw(Canvas canvas) {
int cx = mW / 2;
int cy = mH / 2;
//Log.d("GpsStatusView", String.format("Drawing on a %dx%d canvas", w, h));
canvas.translate(cx, cy);
canvas.rotate(-mRotation);
canvas.drawCircle(0, 0, mW * 0.37125f, gridBorderPaint);
canvas.drawLine(-mW * 0.405f, 0, mW * 0.405f, 0, gridPaint);
canvas.drawLine(0, -mH * 0.405f, 0, mH * 0.405f, gridPaint);
canvas.drawCircle(0, 0, mW * 0.405f, gridPaint);
canvas.drawCircle(0, 0, mW * 0.27f, gridPaint);
canvas.drawCircle(0, 0, mW * 0.135f, gridPaint);
canvas.drawPath(northArrow, northPaint);
canvas.drawTextOnPath(((Activity) getContext()).getString(R.string.value_N),
labelPathN, 0, -labelPaint.descent(), labelPaint);
canvas.drawTextOnPath(((Activity) getContext()).getString(R.string.value_S),
labelPathS, 0, -labelPaint.descent(), labelPaint);
canvas.drawTextOnPath(((Activity) getContext()).getString(R.string.value_E),
labelPathE, 0, -labelPaint.descent(), labelPaint);
canvas.drawTextOnPath(((Activity) getContext()).getString(R.string.value_W),
labelPathW, 0, -labelPaint.descent(), labelPaint);
if (mSats != null) {
for (GpsSatellite sat : mSats) {
drawSat(canvas, sat.getPrn(), sat.getAzimuth(), sat.getElevation(), sat.getSnr(), sat.usedInFix());
}
}
}
@Override
protected void onSizeChanged (int w, int h, int oldw, int oldh) {
mW = w;
mH = h;
refreshGeometries();
}
public void refreshGeometries() {
gridBorderPaint.setStrokeWidth(mW * 0.0625f);
float arrowWidth = 4 * density;
northArrow.reset();
northArrow.moveTo(-arrowWidth, - mH * 0.27f);
northArrow.lineTo(arrowWidth, - mH * 0.27f);
northArrow.lineTo(0, - mH * 0.405f - gridStrokeWidth * 2);
northArrow.close();
labelPaint.setTextSize(mH * 0.045f);
float offsetX = mW * 0.0275f * (float) Math.cos(Math.toRadians(mRotation + 90));
float offsetY = mW * 0.0275f * (float) Math.sin(Math.toRadians(mRotation + 90));
float relX = mW * (float) Math.cos(Math.toRadians(mRotation));
float relY = mH * (float) Math.sin(Math.toRadians(mRotation));
labelPathN.reset();
labelPathN.moveTo(offsetX - relX, - mH * 0.4275f + offsetY - relY);
labelPathN.rLineTo(2 * relX, 2 * relY);
labelPathE.reset();
labelPathE.moveTo(mW * 0.4275f + offsetX - relX, offsetY - relY);
labelPathE.rLineTo(2 * relX, 2 * relY);
labelPathS.reset();
labelPathS.moveTo(offsetX - relX, mH * 0.4275f + offsetY - relY);
labelPathS.rLineTo(2 * relX, 2 * relY);
labelPathW.reset();
labelPathW.moveTo(- mW * 0.4275f + offsetX - relX, offsetY - relY);
labelPathW.rLineTo(2 * relX, 2 * relY);
}
public void setYaw(float yaw) {
mYaw = yaw;
mRotation = mYaw + zeroYaw[((Activity) getContext()).getWindowManager().getDefaultDisplay().getRotation()];
refreshGeometries();
invalidate();
}
public void showSats(Iterable sats) {
mSats = sats;
invalidate();
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/widgets/LocProviderPreference.java
================================================
package com.vonglasow.michael.satstat.widgets;
import java.util.ArrayList;
import java.util.List;
import com.vonglasow.michael.satstat.R;
import android.content.Context;
import android.location.LocationManager;
import android.preference.MultiSelectListPreference;
import android.util.AttributeSet;
public class LocProviderPreference extends MultiSelectListPreference {
private Context mContext;
public LocProviderPreference(Context context) {
super(context);
mContext = context;
setSummary(R.string.pref_loc_prov_summary);
updateProviders();
}
public LocProviderPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
setSummary(R.string.pref_loc_prov_summary);
updateProviders();
}
/**
* Regenerates the list of selectable location providers.
*/
public void updateProviders() {
List providers = ((LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE)).getAllProviders();
if (providers != null) {
List entries = new ArrayList();
List values = new ArrayList();
for (String pr : providers) {
entries.add(pr);
values.add(pr);
}
setEntries(entries.toArray(new CharSequence[]{}));
setEntryValues(values.toArray(new CharSequence[]{}));
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/widgets/MapViewPager.java
================================================
package com.vonglasow.michael.satstat.widgets;
import org.mapsforge.map.android.view.MapView;
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.View;
public class MapViewPager extends ViewPager {
public MapViewPager(Context context) {
super(context);
}
public MapViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* Determines if a child view can be scrolled.
*
* If the child view is scrollable, then true will be returned, indicating
* that scroll operation should not be picked up by the MapViewPager but
* passed on to the child view in which it took place.
*
* This method will return true if the child view is a {@link MapView} and
* retain default behavior in all other cases.
*/
@Override
protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
if (v instanceof MapView) {
return true;
}
return super.canScroll(v, checkV, dx, x, y);
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/widgets/NetworkTypePreference.java
================================================
package com.vonglasow.michael.satstat.widgets;
import java.util.ArrayList;
import java.util.List;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.preference.MultiSelectListPreference;
import android.util.AttributeSet;
public class NetworkTypePreference extends MultiSelectListPreference {
private Context mContext;
public NetworkTypePreference(Context context) {
super(context);
mContext = context;
updateNetworks();
}
public NetworkTypePreference(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
updateNetworks();
}
/**
* Regenerates the list of selectable networks.
*/
public void updateNetworks() {
NetworkInfo[] allnetinfo = ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE)).getAllNetworkInfo();
if (allnetinfo != null) {
List entries = new ArrayList();
List values = new ArrayList();
for (NetworkInfo ni : allnetinfo) {
if ((ni.getType() < ConnectivityManager.TYPE_MOBILE_MMS) || (ni.getType() > ConnectivityManager.TYPE_MOBILE_HIPRI)) {
// filter out specific mobile data connections, we'll catch those with the Mobile setting
entries.add(ni.getTypeName());
//entries.add(ni.getTypeName() + " (" + Integer.toString(ni.getType()) + ")");
values.add(Integer.toString(ni.getType()));
}
}
setEntries(entries.toArray(new CharSequence[]{}));
setEntryValues(values.toArray(new CharSequence[]{}));
}
}
}
================================================
FILE: src/com/vonglasow/michael/satstat/widgets/SquareView.java
================================================
/*
* Copyright © 2013 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package com.vonglasow.michael.satstat.widgets;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
public class SquareView extends View {
private float mRelativeSize = 1;
public SquareView(Context context) {
super(context);
}
public SquareView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SquareView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
int mSize = (int) (Math.min(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)) * mRelativeSize);
setMeasuredDimension(mSize, mSize);
}
public void setRelativeSize(float size) {
mRelativeSize = size;
invalidate();
}
}
================================================
FILE: src/pl/polidea/treeview/AbstractTreeViewAdapter.java
================================================
package pl.polidea.treeview;
import com.vonglasow.michael.satstat.R;
import android.app.Activity;
import android.content.Context;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.FrameLayout.LayoutParams;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
/**
* Adapter used to feed the table view.
*
* @param
* class for ID of the tree
*/
public abstract class AbstractTreeViewAdapter extends BaseAdapter implements
ListAdapter {
private static final String TAG = AbstractTreeViewAdapter.class
.getSimpleName();
private final TreeStateManager treeStateManager;
private final int numberOfLevels;
private final LayoutInflater layoutInflater;
private int indentWidth = 0;
private int indicatorGravity = 0;
private Drawable collapsedDrawable;
private Drawable expandedDrawable;
private Drawable indicatorBackgroundDrawable;
private Drawable rowBackgroundDrawable;
private boolean collapsible;
private final Activity activity;
public Activity getActivity() {
return activity;
}
protected TreeStateManager getManager() {
return treeStateManager;
}
protected void expandCollapse(final T id) {
final TreeNodeInfo info = treeStateManager.getNodeInfo(id);
if (!info.isWithChildren()) {
// ignore - no default action
return;
}
if (info.isExpanded()) {
treeStateManager.collapseChildren(id);
} else {
treeStateManager.expandDirectChildren(id);
}
}
private void calculateIndentWidth() {
if (expandedDrawable != null) {
indentWidth = Math.max(getIndentWidth(),
expandedDrawable.getIntrinsicWidth());
}
if (collapsedDrawable != null) {
indentWidth = Math.max(getIndentWidth(),
collapsedDrawable.getIntrinsicWidth());
}
}
public AbstractTreeViewAdapter(final Activity activity,
final TreeStateManager treeStateManager, final int numberOfLevels) {
this.activity = activity;
this.treeStateManager = treeStateManager;
this.layoutInflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.numberOfLevels = numberOfLevels;
this.collapsedDrawable = null;
this.expandedDrawable = null;
this.rowBackgroundDrawable = null;
this.indicatorBackgroundDrawable = null;
}
@Override
public void registerDataSetObserver(final DataSetObserver observer) {
treeStateManager.registerDataSetObserver(observer);
}
@Override
public void unregisterDataSetObserver(final DataSetObserver observer) {
treeStateManager.unregisterDataSetObserver(observer);
}
@Override
public int getCount() {
return treeStateManager.getVisibleCount();
}
@Override
public Object getItem(final int position) {
return getTreeId(position);
}
public T getTreeId(final int position) {
return treeStateManager.getVisibleList().get(position);
}
public TreeNodeInfo getTreeNodeInfo(final int position) {
return treeStateManager.getNodeInfo(getTreeId(position));
}
@Override
public boolean hasStableIds() { // NOPMD
return true;
}
@Override
public int getItemViewType(final int position) {
return getTreeNodeInfo(position).getLevel();
}
@Override
public int getViewTypeCount() {
return numberOfLevels;
}
@Override
public boolean isEmpty() {
return getCount() == 0;
}
@Override
public boolean areAllItemsEnabled() { // NOPMD
return true;
}
@Override
public boolean isEnabled(final int position) { // NOPMD
return true;
}
protected int getTreeListItemWrapperId() {
return R.layout.tree_list_item_wrapper;
}
@Override
public final View getView(final int position, final View convertView,
final ViewGroup parent) {
//Log.d(TAG, "Creating a view based on " + convertView
// + " with position " + position);
final TreeNodeInfo nodeInfo = getTreeNodeInfo(position);
if (convertView == null) {
//Log.d(TAG, "Creating the view a new");
final LinearLayout layout = (LinearLayout) layoutInflater.inflate(
getTreeListItemWrapperId(), null);
return populateTreeItem(layout, getNewChildView(nodeInfo),
nodeInfo, true);
} else {
//Log.d(TAG, "Reusing the view");
final LinearLayout linear = (LinearLayout) convertView;
final FrameLayout frameLayout = (FrameLayout) linear
.findViewById(R.id.treeview_list_item_frame);
final View childView = frameLayout.getChildAt(0);
updateView(childView, nodeInfo);
return populateTreeItem(linear, childView, nodeInfo, false);
}
}
/**
* Called when new view is to be created.
*
* @param treeNodeInfo
* node info
* @return view that should be displayed as tree content
*/
public abstract View getNewChildView(TreeNodeInfo treeNodeInfo);
/**
* Called when new view is going to be reused. You should update the view
* and fill it in with the data required to display the new information. You
* can also create a new view, which will mean that the old view will not be
* reused.
*
* @param view
* view that should be updated with the new values
* @param treeNodeInfo
* node info used to populate the view
* @return view to used as row indented content
*/
public abstract View updateView(View view, TreeNodeInfo treeNodeInfo);
/**
* Retrieves background drawable for the node.
*
* @param treeNodeInfo
* node info
* @return drawable returned as background for the whole row. Might be null,
* then default background is used
*/
public Drawable getBackgroundDrawable(final TreeNodeInfo treeNodeInfo) { // NOPMD
return null;
}
private Drawable getDrawableOrDefaultBackground(final Drawable r) {
if (r == null) {
return activity.getResources()
.getDrawable(R.drawable.list_selector_background).mutate();
} else {
return r;
}
}
public final LinearLayout populateTreeItem(final LinearLayout layout,
final View childView, final TreeNodeInfo nodeInfo,
final boolean newChildView) {
final Drawable individualRowDrawable = getBackgroundDrawable(nodeInfo);
layout.setBackgroundDrawable(individualRowDrawable == null ? getDrawableOrDefaultBackground(rowBackgroundDrawable)
: individualRowDrawable);
final LinearLayout.LayoutParams indicatorLayoutParams = new LinearLayout.LayoutParams(
calculateIndentation(nodeInfo), LayoutParams.FILL_PARENT);
final LinearLayout indicatorLayout = (LinearLayout) layout
.findViewById(R.id.treeview_list_item_image_layout);
indicatorLayout.setGravity(indicatorGravity);
indicatorLayout.setLayoutParams(indicatorLayoutParams);
final ImageView image = (ImageView) layout
.findViewById(R.id.treeview_list_item_image);
image.setImageDrawable(getDrawable(nodeInfo));
image.setScaleType(ScaleType.CENTER);
image.setTag(nodeInfo.getId());
image.setOnClickListener(null);
image.setClickable(false);
layout.setTag(nodeInfo.getId());
final FrameLayout frameLayout = (FrameLayout) layout
.findViewById(R.id.treeview_list_item_frame);
final FrameLayout.LayoutParams childParams = new FrameLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
if (newChildView) {
frameLayout.addView(childView, childParams);
}
frameLayout.setTag(nodeInfo.getId());
return layout;
}
protected int calculateIndentation(final TreeNodeInfo nodeInfo) {
return getIndentWidth() * (nodeInfo.getLevel() + (collapsible ? 1 : 0));
}
protected Drawable getDrawable(final TreeNodeInfo nodeInfo) {
if (!nodeInfo.isWithChildren() || !collapsible) {
return getDrawableOrDefaultBackground(indicatorBackgroundDrawable);
}
if (nodeInfo.isExpanded()) {
return expandedDrawable;
} else {
return collapsedDrawable;
}
}
public void setIndicatorGravity(final int indicatorGravity) {
this.indicatorGravity = indicatorGravity;
}
public void setCollapsedDrawable(final Drawable collapsedDrawable) {
this.collapsedDrawable = collapsedDrawable;
calculateIndentWidth();
}
public void setExpandedDrawable(final Drawable expandedDrawable) {
this.expandedDrawable = expandedDrawable;
calculateIndentWidth();
}
public void setIndentWidth(final int indentWidth) {
this.indentWidth = indentWidth;
calculateIndentWidth();
}
public void setRowBackgroundDrawable(final Drawable rowBackgroundDrawable) {
this.rowBackgroundDrawable = rowBackgroundDrawable;
}
public void setIndicatorBackgroundDrawable(
final Drawable indicatorBackgroundDrawable) {
this.indicatorBackgroundDrawable = indicatorBackgroundDrawable;
}
public void setCollapsible(final boolean collapsible) {
this.collapsible = collapsible;
}
public void refresh() {
treeStateManager.refresh();
}
private int getIndentWidth() {
return indentWidth;
}
@SuppressWarnings("unchecked")
public void handleItemClick(final View view, final Object id) {
expandCollapse((T) id);
}
}
================================================
FILE: src/pl/polidea/treeview/DownloadTreeStateManager.java
================================================
/*
* Copyright (c) 2011, Polidea
* Copyright © 2013–2016 Michael von Glasow.
*
* This file is part of LSRN Tools.
*
* LSRN Tools is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LSRN Tools is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LSRN Tools. If not, see .
*/
package pl.polidea.treeview;
import java.util.List;
import com.vonglasow.michael.satstat.utils.RemoteFile;
/**
* In-memory manager of tree state, adapted for use with {@link com.conglasow.michael.satstat.utils.RemoteFile}
* and branches loaded dynamically.
*/
public class DownloadTreeStateManager extends
InMemoryTreeStateManager {
private static final long serialVersionUID = 1L;
@Override
public synchronized TreeNodeInfo getNodeInfo(final RemoteFile id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrow(id);
final List> children = node.getChildren();
boolean expanded = false;
if (!children.isEmpty() && children.get(0).isVisible()) {
expanded = true;
}
boolean hasChildren = id.isDirectory | !children.isEmpty();
return new TreeNodeInfo(id, node.getLevel(), hasChildren,
node.isVisible(), expanded);
}
}
================================================
FILE: src/pl/polidea/treeview/InMemoryTreeNode.java
================================================
package pl.polidea.treeview;
import java.io.Serializable;
import java.util.LinkedList;
import java.util.List;
/**
* Node. It is package protected so that it cannot be used outside.
*
* @param
* type of the identifier used by the tree
*/
class InMemoryTreeNode implements Serializable {
private static final long serialVersionUID = 1L;
private final T id;
private final T parent;
private final int level;
private boolean visible = true;
private final List> children = new LinkedList>();
private List childIdListCache = null;
public InMemoryTreeNode(final T id, final T parent, final int level,
final boolean visible) {
super();
this.id = id;
this.parent = parent;
this.level = level;
this.visible = visible;
}
public int indexOf(final T id) {
return getChildIdList().indexOf(id);
}
/**
* Cache is built lasily only if needed. The cache is cleaned on any
* structure change for that node!).
*
* @return list of ids of children
*/
public synchronized List getChildIdList() {
if (childIdListCache == null) {
childIdListCache = new LinkedList();
for (final InMemoryTreeNode n : children) {
childIdListCache.add(n.getId());
}
}
return childIdListCache;
}
public boolean isVisible() {
return visible;
}
public void setVisible(final boolean visible) {
this.visible = visible;
}
public int getChildrenListSize() {
return children.size();
}
public synchronized InMemoryTreeNode add(final int index, final T child,
final boolean visible) {
childIdListCache = null;
// Note! top levell children are always visible (!)
final InMemoryTreeNode newNode = new InMemoryTreeNode(child,
getId(), getLevel() + 1, getId() == null ? true : visible);
children.add(index, newNode);
return newNode;
}
/**
* Note. This method should technically return unmodifiable collection, but
* for performance reason on small devices we do not do it.
*
* @return children list
*/
public List> getChildren() {
return children;
}
public synchronized void clearChildren() {
children.clear();
childIdListCache = null;
}
public synchronized void removeChild(final T child) {
final int childIndex = indexOf(child);
if (childIndex != -1) {
children.remove(childIndex);
childIdListCache = null;
}
}
@Override
public String toString() {
return "InMemoryTreeNode [id=" + getId() + ", parent=" + getParent()
+ ", level=" + getLevel() + ", visible=" + visible
+ ", children=" + children + ", childIdListCache="
+ childIdListCache + "]";
}
T getId() {
return id;
}
T getParent() {
return parent;
}
int getLevel() {
return level;
}
}
================================================
FILE: src/pl/polidea/treeview/InMemoryTreeStateManager.java
================================================
package pl.polidea.treeview;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.database.DataSetObserver;
import android.util.Log;
/**
* In-memory manager of tree state.
*
* @param
* type of identifier
*/
public class InMemoryTreeStateManager implements TreeStateManager {
private static final String TAG = InMemoryTreeStateManager.class
.getSimpleName();
private static final long serialVersionUID = 1L;
private final Map> allNodes = new HashMap>();
private final InMemoryTreeNode topSentinel = new InMemoryTreeNode(
null, null, -1, true);
private transient List visibleListCache = null; // lasy initialised
private transient List unmodifiableVisibleList = null;
private boolean visibleByDefault = true;
private transient Set observers = null;
private synchronized void internalDataSetChanged() {
visibleListCache = null;
unmodifiableVisibleList = null;
if (observers != null)
for (final DataSetObserver observer : observers) {
observer.onChanged();
}
}
/**
* If true new nodes are visible by default.
*
* @param visibleByDefault
* if true, then newly added nodes are expanded by default
*/
public void setVisibleByDefault(final boolean visibleByDefault) {
this.visibleByDefault = visibleByDefault;
}
protected InMemoryTreeNode getNodeFromTreeOrThrow(final T id) {
if (id == null) {
throw new NodeNotInTreeException("(null)");
}
final InMemoryTreeNode node = allNodes.get(id);
if (node == null) {
throw new NodeNotInTreeException(id.toString());
}
return node;
}
private InMemoryTreeNode getNodeFromTreeOrThrowAllowRoot(final T id) {
if (id == null) {
return topSentinel;
}
return getNodeFromTreeOrThrow(id);
}
private void expectNodeNotInTreeYet(final T id) {
final InMemoryTreeNode node = allNodes.get(id);
if (node != null) {
throw new NodeAlreadyInTreeException(id.toString(), node.toString());
}
}
@Override
public synchronized TreeNodeInfo getNodeInfo(final T id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrow(id);
final List> children = node.getChildren();
boolean expanded = false;
if (!children.isEmpty() && children.get(0).isVisible()) {
expanded = true;
}
return new TreeNodeInfo(id, node.getLevel(), !children.isEmpty(),
node.isVisible(), expanded);
}
@Override
public synchronized List getChildren(final T id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
return node.getChildIdList();
}
@Override
public synchronized T getParent(final T id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
return node.getParent();
}
private boolean getChildrenVisibility(final InMemoryTreeNode node) {
boolean visibility;
final List> children = node.getChildren();
if (children.isEmpty()) {
visibility = visibleByDefault;
} else {
visibility = children.get(0).isVisible();
}
return visibility;
}
@Override
public synchronized void addBeforeChild(final T parent, final T newChild,
final T beforeChild) {
expectNodeNotInTreeYet(newChild);
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(parent);
final boolean visibility = getChildrenVisibility(node);
// top nodes are always expanded.
if (beforeChild == null) {
final InMemoryTreeNode added = node.add(0, newChild, visibility);
allNodes.put(newChild, added);
} else {
final int index = node.indexOf(beforeChild);
final InMemoryTreeNode added = node.add(index == -1 ? 0 : index,
newChild, visibility);
allNodes.put(newChild, added);
}
if (visibility) {
internalDataSetChanged();
}
}
@Override
public synchronized void addAfterChild(final T parent, final T newChild,
final T afterChild) {
expectNodeNotInTreeYet(newChild);
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(parent);
final boolean visibility = getChildrenVisibility(node);
if (afterChild == null) {
final InMemoryTreeNode added = node.add(
node.getChildrenListSize(), newChild, visibility);
allNodes.put(newChild, added);
} else {
final int index = node.indexOf(afterChild);
final InMemoryTreeNode added = node.add(
index == -1 ? node.getChildrenListSize() : index + 1, newChild,
visibility);
allNodes.put(newChild, added);
}
if (visibility) {
internalDataSetChanged();
}
}
@Override
public synchronized void removeNodeRecursively(final T id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
final boolean visibleNodeChanged = removeNodeRecursively(node);
final T parent = node.getParent();
final InMemoryTreeNode parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
parentNode.removeChild(id);
if (visibleNodeChanged) {
internalDataSetChanged();
}
}
private boolean removeNodeRecursively(final InMemoryTreeNode node) {
boolean visibleNodeChanged = false;
for (final InMemoryTreeNode child : node.getChildren()) {
if (removeNodeRecursively(child)) {
visibleNodeChanged = true;
}
}
node.clearChildren();
if (node.getId() != null) {
allNodes.remove(node.getId());
if (node.isVisible()) {
visibleNodeChanged = true;
}
}
return visibleNodeChanged;
}
private void setChildrenVisibility(final InMemoryTreeNode node,
final boolean visible, final boolean recursive) {
for (final InMemoryTreeNode child : node.getChildren()) {
child.setVisible(visible);
if (recursive) {
setChildrenVisibility(child, visible, true);
}
}
}
@Override
public synchronized void expandDirectChildren(final T id) {
Log.d(TAG, "Expanding direct children of " + id);
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
setChildrenVisibility(node, true, false);
internalDataSetChanged();
}
@Override
public synchronized void expandEverythingBelow(final T id) {
Log.d(TAG, "Expanding all children below " + id);
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
setChildrenVisibility(node, true, true);
internalDataSetChanged();
}
@Override
public synchronized void collapseChildren(final T id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
if (node == topSentinel) {
for (final InMemoryTreeNode n : topSentinel.getChildren()) {
setChildrenVisibility(n, false, true);
}
} else {
setChildrenVisibility(node, false, true);
}
internalDataSetChanged();
}
@Override
public synchronized T getNextSibling(final T id) {
final T parent = getParent(id);
final InMemoryTreeNode parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
boolean returnNext = false;
for (final InMemoryTreeNode child : parentNode.getChildren()) {
if (returnNext) {
return child.getId();
}
if (child.getId().equals(id)) {
returnNext = true;
}
}
return null;
}
@Override
public synchronized T getPreviousSibling(final T id) {
final T parent = getParent(id);
final InMemoryTreeNode parentNode = getNodeFromTreeOrThrowAllowRoot(parent);
T previousSibling = null;
for (final InMemoryTreeNode child : parentNode.getChildren()) {
if (child.getId().equals(id)) {
return previousSibling;
}
previousSibling = child.getId();
}
return null;
}
@Override
public synchronized boolean isInTree(final T id) {
return allNodes.containsKey(id);
}
@Override
public synchronized int getVisibleCount() {
return getVisibleList().size();
}
@Override
public synchronized List getVisibleList() {
T currentId = null;
if (visibleListCache == null) {
visibleListCache = new ArrayList(allNodes.size());
do {
currentId = getNextVisible(currentId);
if (currentId == null) {
break;
} else {
visibleListCache.add(currentId);
}
} while (true);
}
if (unmodifiableVisibleList == null) {
unmodifiableVisibleList = Collections
.unmodifiableList(visibleListCache);
}
return unmodifiableVisibleList;
}
public synchronized T getNextVisible(final T id) {
final InMemoryTreeNode node = getNodeFromTreeOrThrowAllowRoot(id);
if (!node.isVisible()) {
return null;
}
final List> children = node.getChildren();
if (!children.isEmpty()) {
final InMemoryTreeNode firstChild = children.get(0);
if (firstChild.isVisible()) {
return firstChild.getId();
}
}
final T sibl = getNextSibling(id);
if (sibl != null) {
return sibl;
}
T parent = node.getParent();
do {
if (parent == null) {
return null;
}
final T parentSibling = getNextSibling(parent);
if (parentSibling != null) {
return parentSibling;
}
parent = getNodeFromTreeOrThrow(parent).getParent();
} while (true);
}
@Override
public synchronized void registerDataSetObserver(
final DataSetObserver observer) {
if (observers == null)
observers = new HashSet();
observers.add(observer);
}
@Override
public synchronized void unregisterDataSetObserver(
final DataSetObserver observer) {
if (observers != null)
observers.remove(observer);
}
@Override
public int getLevel(final T id) {
return getNodeFromTreeOrThrow(id).getLevel();
}
@Override
public Integer[] getHierarchyDescription(final T id) {
final int level = getLevel(id);
final Integer[] hierarchy = new Integer[level + 1];
int currentLevel = level;
T currentId = id;
T parent = getParent(currentId);
while (currentLevel >= 0) {
hierarchy[currentLevel--] = getChildren(parent).indexOf(currentId);
currentId = parent;
parent = getParent(parent);
}
return hierarchy;
}
private void appendToSb(final StringBuilder sb, final T id) {
if (id != null) {
final TreeNodeInfo node = getNodeInfo(id);
final int indent = node.getLevel() * 4;
final char[] indentString = new char[indent];
Arrays.fill(indentString, ' ');
sb.append(indentString);
sb.append(node.toString());
sb.append(Arrays.asList(getHierarchyDescription(id)).toString());
sb.append("\n");
}
final List children = getChildren(id);
for (final T child : children) {
appendToSb(sb, child);
}
}
@Override
public synchronized String toString() {
final StringBuilder sb = new StringBuilder();
appendToSb(sb, null);
return sb.toString();
}
@Override
public synchronized void clear() {
allNodes.clear();
topSentinel.clearChildren();
internalDataSetChanged();
}
@Override
public void refresh() {
internalDataSetChanged();
}
}
================================================
FILE: src/pl/polidea/treeview/NodeAlreadyInTreeException.java
================================================
package pl.polidea.treeview;
/**
* The node being added is already in the tree.
*
*/
public class NodeAlreadyInTreeException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NodeAlreadyInTreeException(final String id, final String oldNode) {
super("The node has already been added to the tree: " + id + ". Old node is:" + oldNode);
}
}
================================================
FILE: src/pl/polidea/treeview/NodeNotInTreeException.java
================================================
package pl.polidea.treeview;
/**
* This exception is thrown when the tree does not contain node requested.
*
*/
public class NodeNotInTreeException extends RuntimeException {
private static final long serialVersionUID = 1L;
public NodeNotInTreeException(final String id) {
super("The tree does not contain the node specified: " + id);
}
}
================================================
FILE: src/pl/polidea/treeview/TreeBuilder.java
================================================
package pl.polidea.treeview;
import android.util.Log;
/**
* Allows to build tree easily in sequential mode (you have to know levels of
* all the tree elements upfront). You should rather use this class rather than
* manager if you build initial tree from some external data source.
*
* Note, that all ids must be unique. IDs are used to find nodes in the whole
* tree, so they cannot repeat even if they are in different
* sub-trees.
*
* @param
*/
public class TreeBuilder {
private static final String TAG = TreeBuilder.class.getSimpleName();
private final TreeStateManager manager;
private T lastAddedId = null;
private int lastLevel = -1;
public TreeBuilder(final TreeStateManager manager) {
this.manager = manager;
}
public void clear() {
manager.clear();
lastAddedId = null;
lastLevel = -1;
}
/**
* Adds new relation to existing tree. Child is set as the last child of the
* parent node. Parent has to already exist in the tree, child cannot yet
* exist. This method is mostly useful in case you add entries layer by
* layer - i.e. first top level entries, then children for all parents, then
* grand-children and so on.
*
* @param parent
* parent id
* @param child
* child id
*/
public synchronized void addRelation(final T parent, final T child) {
//Log.d(TAG, "Adding relation parent:" + parent + " -> child: " + child);
manager.addAfterChild(parent, child, null);
lastAddedId = child;
lastLevel = manager.getLevel(child);
}
/**
* Adds sequentially new node. Using this method is the simplest way of
* building tree - if you have all the elements in the sequence as they
* should be displayed in fully-expanded tree. You can combine it with add
* relation - for example you can add information about few levels using
* {@link addRelation} and then after the right level is added as parent,
* you can continue adding them using sequential operation.
*
* @param id
* id of the node
* @param level
* its level
*/
public synchronized void sequentiallyAddNextNode(final T id, final int level) {
//Log.d(TAG, "Adding sequentiall node " + id + " at level " + level);
if (lastAddedId == null) {
addNodeToParentOneLevelDown(null, id, level);
} else {
if (level <= lastLevel) {
final T parent = findParentAtLevel(lastAddedId, level - 1);
addNodeToParentOneLevelDown(parent, id, level);
} else {
addNodeToParentOneLevelDown(lastAddedId, id, level);
}
}
}
/**
* Find parent of the node at the level specified.
*
* @param node
* node from which we start
* @param levelToFind
* level which we are looking for
* @return the node found (null if it is topmost node).
*/
private T findParentAtLevel(final T node, final int levelToFind) {
T parent = manager.getParent(node);
while (parent != null) {
if (manager.getLevel(parent) == levelToFind) {
break;
}
parent = manager.getParent(parent);
}
return parent;
}
/**
* Adds note to parent at the level specified. But it verifies that the
* level is one level down than the parent!
*
* @param parent
* parent parent
* @param id
* new node id
* @param level
* should always be parent's level + 1
*/
private void addNodeToParentOneLevelDown(final T parent, final T id,
final int level) {
if (parent == null && level != 0) {
throw new TreeConfigurationException("Trying to add new id " + id
+ " to top level with level != 0 (" + level + ")");
}
if (parent != null && manager.getLevel(parent) != level - 1) {
throw new TreeConfigurationException("Trying to add new id " + id
+ " <" + level + "> to " + parent + " <"
+ manager.getLevel(parent)
+ ">. The difference in levels up is bigger than 1.");
}
manager.addAfterChild(parent, id, null);
setLastAdded(id, level);
}
private void setLastAdded(final T id, final int level) {
lastAddedId = id;
lastLevel = level;
}
}
================================================
FILE: src/pl/polidea/treeview/TreeConfigurationException.java
================================================
package pl.polidea.treeview;
/**
* Exception thrown when there is a problem with configuring tree.
*
*/
public class TreeConfigurationException extends RuntimeException {
private static final long serialVersionUID = 1L;
public TreeConfigurationException(final String detailMessage) {
super(detailMessage);
}
}
================================================
FILE: src/pl/polidea/treeview/TreeNodeInfo.java
================================================
package pl.polidea.treeview;
/**
* Information about the node.
*
* @param
* type of the id for the tree
*/
public class TreeNodeInfo {
private final T id;
private final int level;
private final boolean withChildren;
private final boolean visible;
private final boolean expanded;
/**
* Creates the node information.
*
* @param id
* id of the node
* @param level
* level of the node
* @param withChildren
* whether the node has children.
* @param visible
* whether the tree node is visible.
* @param expanded
* whether the tree node is expanded
*
*/
public TreeNodeInfo(final T id, final int level,
final boolean withChildren, final boolean visible,
final boolean expanded) {
super();
this.id = id;
this.level = level;
this.withChildren = withChildren;
this.visible = visible;
this.expanded = expanded;
}
public T getId() {
return id;
}
public boolean isWithChildren() {
return withChildren;
}
public boolean isVisible() {
return visible;
}
public boolean isExpanded() {
return expanded;
}
public int getLevel() {
return level;
}
@Override
public String toString() {
return "TreeNodeInfo [id=" + id + ", level=" + level
+ ", withChildren=" + withChildren + ", visible=" + visible
+ ", expanded=" + expanded + "]";
}
}
================================================
FILE: src/pl/polidea/treeview/TreeStateManager.java
================================================
package pl.polidea.treeview;
import java.io.Serializable;
import java.util.List;
import android.database.DataSetObserver;
/**
* Manages information about state of the tree. It only keeps information about
* tree elements, not the elements themselves.
*
* @param
* type of the identifier for nodes in the tree
*/
public interface TreeStateManager extends Serializable {
/**
* Returns array of integers showing the location of the node in hierarchy.
* It corresponds to heading numbering. {0,0,0} in 3 level node is the first
* node {0,0,1} is second leaf (assuming that there are two leaves in first
* subnode of the first node).
*
* @param id
* id of the node
* @return textual description of the hierarchy in tree for the node.
*/
Integer[] getHierarchyDescription(T id);
/**
* Returns level of the node.
*
* @param id
* id of the node
* @return level in the tree
*/
int getLevel(T id);
/**
* Returns information about the node.
*
* @param id
* node id
* @return node info
*/
TreeNodeInfo getNodeInfo(T id);
/**
* Returns children of the node.
*
* @param id
* id of the node or null if asking for top nodes
* @return children of the node
*/
List getChildren(T id);
/**
* Returns parent of the node.
*
* @param id
* id of the node
* @return parent id or null if no parent
*/
T getParent(T id);
/**
* Adds the node before child or at the beginning.
*
* @param parent
* id of the parent node. If null - adds at the top level
* @param newChild
* new child to add if null - adds at the beginning.
* @param beforeChild
* child before which to add the new child
*/
void addBeforeChild(T parent, T newChild, T beforeChild);
/**
* Adds the node after child or at the end.
*
* @param parent
* id of the parent node. If null - adds at the top level.
* @param newChild
* new child to add. If null - adds at the end.
* @param afterChild
* child after which to add the new child
*/
void addAfterChild(T parent, T newChild, T afterChild);
/**
* Removes the node and all children from the tree.
*
* @param id
* id of the node to remove or null if all nodes are to be
* removed.
*/
void removeNodeRecursively(T id);
/**
* Expands all children of the node.
*
* @param id
* node which children should be expanded. cannot be null (top
* nodes are always expanded!).
*/
void expandDirectChildren(T id);
/**
* Expands everything below the node specified. Might be null - then expands
* all.
*
* @param id
* node which children should be expanded or null if all nodes
* are to be expanded.
*/
void expandEverythingBelow(T id);
/**
* Collapse children.
*
* @param id
* id collapses everything below node specified. If null,
* collapses everything but top-level nodes.
*/
void collapseChildren(T id);
/**
* Returns next sibling of the node (or null if no further sibling).
*
* @param id
* node id
* @return the sibling (or null if no next)
*/
T getNextSibling(T id);
/**
* Returns previous sibling of the node (or null if no previous sibling).
*
* @param id
* node id
* @return the sibling (or null if no previous)
*/
T getPreviousSibling(T id);
/**
* Checks if given node is already in tree.
*
* @param id
* id of the node
* @return true if node is already in tree.
*/
boolean isInTree(T id);
/**
* Count visible elements.
*
* @return number of currently visible elements.
*/
int getVisibleCount();
/**
* Returns visible node list.
*
* @return return the list of all visible nodes in the right sequence
*/
List getVisibleList();
/**
* Registers observers with the manager.
*
* @param observer
* observer
*/
void registerDataSetObserver(final DataSetObserver observer);
/**
* Unregisters observers with the manager.
*
* @param observer
* observer
*/
void unregisterDataSetObserver(final DataSetObserver observer);
/**
* Cleans tree stored in manager. After this operation the tree is empty.
*
*/
void clear();
/**
* Refreshes views connected to the manager.
*/
void refresh();
}
================================================
FILE: src/pl/polidea/treeview/TreeViewList.java
================================================
package pl.polidea.treeview;
import com.vonglasow.michael.satstat.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
/**
* Tree view, expandable multi-level.
*
*
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_collapsible
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_src_expanded
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_src_collapsed
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_indent_width
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_handle_trackball_press
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_indicator_gravity
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_indicator_background
* attr ref pl.polidea.treeview.R.styleable#TreeViewList_row_background
*
*/
public class TreeViewList extends ListView {
private static final int DEFAULT_COLLAPSED_RESOURCE = R.drawable.collapsed;
private static final int DEFAULT_EXPANDED_RESOURCE = R.drawable.expanded;
private static final int DEFAULT_INDENT = 0;
private static final int DEFAULT_GRAVITY = Gravity.LEFT
| Gravity.CENTER_VERTICAL;
private Drawable expandedDrawable;
private Drawable collapsedDrawable;
private Drawable rowBackgroundDrawable;
private Drawable indicatorBackgroundDrawable;
private int indentWidth = 0;
private int indicatorGravity = 0;
private AbstractTreeViewAdapter< ? > treeAdapter;
private boolean collapsible;
private boolean handleTrackballPress;
public TreeViewList(final Context context, final AttributeSet attrs) {
this(context, attrs, R.style.treeViewListStyle);
}
public TreeViewList(final Context context) {
this(context, null);
}
public TreeViewList(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
parseAttributes(context, attrs);
}
private void parseAttributes(final Context context, final AttributeSet attrs) {
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.TreeViewList);
expandedDrawable = a.getDrawable(R.styleable.TreeViewList_src_expanded);
if (expandedDrawable == null) {
expandedDrawable = context.getResources().getDrawable(
DEFAULT_EXPANDED_RESOURCE);
}
collapsedDrawable = a
.getDrawable(R.styleable.TreeViewList_src_collapsed);
if (collapsedDrawable == null) {
collapsedDrawable = context.getResources().getDrawable(
DEFAULT_COLLAPSED_RESOURCE);
}
indentWidth = a.getDimensionPixelSize(
R.styleable.TreeViewList_indent_width, DEFAULT_INDENT);
indicatorGravity = a.getInteger(
R.styleable.TreeViewList_indicator_gravity, DEFAULT_GRAVITY);
indicatorBackgroundDrawable = a
.getDrawable(R.styleable.TreeViewList_indicator_background);
rowBackgroundDrawable = a
.getDrawable(R.styleable.TreeViewList_row_background);
collapsible = a.getBoolean(R.styleable.TreeViewList_collapsible, true);
handleTrackballPress = a.getBoolean(
R.styleable.TreeViewList_handle_trackball_press, true);
}
@Override
public void setAdapter(final ListAdapter adapter) {
if (!(adapter instanceof AbstractTreeViewAdapter)) {
throw new TreeConfigurationException(
"The adapter is not of TreeViewAdapter type");
}
treeAdapter = (AbstractTreeViewAdapter< ? >) adapter;
syncAdapter();
super.setAdapter(treeAdapter);
}
private void syncAdapter() {
treeAdapter.setCollapsedDrawable(collapsedDrawable);
treeAdapter.setExpandedDrawable(expandedDrawable);
treeAdapter.setIndicatorGravity(indicatorGravity);
treeAdapter.setIndentWidth(indentWidth);
treeAdapter.setIndicatorBackgroundDrawable(indicatorBackgroundDrawable);
treeAdapter.setRowBackgroundDrawable(rowBackgroundDrawable);
treeAdapter.setCollapsible(collapsible);
if (handleTrackballPress) {
setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(final AdapterView< ? > parent,
final View view, final int position, final long id) {
treeAdapter.handleItemClick(view, view.getTag());
}
});
} else {
setOnClickListener(null);
}
}
public void setExpandedDrawable(final Drawable expandedDrawable) {
this.expandedDrawable = expandedDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setCollapsedDrawable(final Drawable collapsedDrawable) {
this.collapsedDrawable = collapsedDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setRowBackgroundDrawable(final Drawable rowBackgroundDrawable) {
this.rowBackgroundDrawable = rowBackgroundDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setIndicatorBackgroundDrawable(
final Drawable indicatorBackgroundDrawable) {
this.indicatorBackgroundDrawable = indicatorBackgroundDrawable;
syncAdapter();
treeAdapter.refresh();
}
public void setIndentWidth(final int indentWidth) {
this.indentWidth = indentWidth;
syncAdapter();
treeAdapter.refresh();
}
public void setIndicatorGravity(final int indicatorGravity) {
this.indicatorGravity = indicatorGravity;
syncAdapter();
treeAdapter.refresh();
}
public void setCollapsible(final boolean collapsible) {
this.collapsible = collapsible;
syncAdapter();
treeAdapter.refresh();
}
public void setHandleTrackballPress(final boolean handleTrackballPress) {
this.handleTrackballPress = handleTrackballPress;
syncAdapter();
treeAdapter.refresh();
}
public Drawable getExpandedDrawable() {
return expandedDrawable;
}
public Drawable getCollapsedDrawable() {
return collapsedDrawable;
}
public Drawable getRowBackgroundDrawable() {
return rowBackgroundDrawable;
}
public Drawable getIndicatorBackgroundDrawable() {
return indicatorBackgroundDrawable;
}
public int getIndentWidth() {
return indentWidth;
}
public int getIndicatorGravity() {
return indicatorGravity;
}
public boolean isCollapsible() {
return collapsible;
}
public boolean isHandleTrackballPress() {
return handleTrackballPress;
}
}
================================================
FILE: src/pl/polidea/treeview/overview.html
================================================
This is a small utility that provides quite configurable tree view list.
It is based on standard android list view. It separates out different
aspects of the tree: there is a separate list view, tree adapter, tree
state manager and tree state builder.
Tree view provides the frame to display the view.
Adapter allows to create visual representation of each tree
node.
State manager provides storage for tree state (connections
between parents and children, collapsed/expanded state). It provides
all the low-level tree manipulation methods.
Tree builder allows to build tree easily providing higher
level methods. The tree can be build either from prepared sequentially
prepared list of nodes (node id, level) or using (parent/child
relationships).
For now only in-memory state manager is provided, but Tree State
Manger interface is done in the way that database tree manager even for
large trees is potentially supported.
================================================
FILE: src/pl/polidea/treeview/package-info.java
================================================
/**
* Provides expandable Tree View implementation.
*/
package pl.polidea.treeview;
================================================
FILE: src/uk/me/jstott/jcoord/CoordinateSystem.java
================================================
package uk.me.jstott.jcoord;
import uk.me.jstott.jcoord.datum.Datum;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Superclass for classes defining co-ordinate systems.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public abstract class CoordinateSystem {
private Datum datum;
public CoordinateSystem(Datum datum) {
setDatum(datum);
}
/**
* Convert a co-ordinate in the co-ordinate system to a point represented
* by a latitude and longitude and a perpendicular height above (or below) a
* reference ellipsoid.
*
* @return a LatLng representation of a point in a co-ordinate system.
* @since 1.1
*/
public abstract LatLng toLatLng();
/**
* Set the datum.
*
* @param datum the datum.
* @since 1.1
*/
public void setDatum(Datum datum) {
this.datum = datum;
}
/**
* Get the datum.
*
* @return the datum.
* @since 1.1
*/
public Datum getDatum() {
return datum;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ECEFRef.java
================================================
package uk.me.jstott.jcoord;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.datum.WGS84Datum;
import uk.me.jstott.jcoord.ellipsoid.Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* ECEF (earth-centred, earth-fixed) Cartesian co-ordinates are used to define a
* point in three-dimensional space. ECEF co-ordinates are defined relative to
* an x-axis (the intersection of the equatorial plane and the plane defined by
* the prime meridian), a y-axis (at 90° to the x-axis and its intersection
* with the equator) and a z-axis (intersecting the North Pole). All the axes
* intersect at the point defined by the centre of mass of the Earth.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class ECEFRef extends CoordinateSystem {
/**
* x co-ordinate in metres.
*/
private double x;
/**
* y co-ordinate in metres.
*/
private double y;
/**
* z co-ordinate in metres.
*/
private double z;
/**
* Create a new earth-centred, earth-fixed (ECEF) reference from the given
* parameters using the WGS84 reference ellipsoid.
*
* @param x
* the x co-ordinate.
* @param y
* the y co-ordinate.
* @param z
* the z co-ordinate.
*/
public ECEFRef(double x, double y, double z) {
this(x, y, z, new WGS84Datum());
}
/**
* Create a new earth-centred, earth-fixed (ECEF) reference from the given
* parameters and the given reference ellipsoid.
*
* @param x
* the x co-ordinate.
* @param y
* the y co-ordinate.
* @param z
* the z co-ordinate.
* @param datum
* the datum.
* @since 1.1
*/
public ECEFRef(double x, double y, double z, Datum datum) {
super(datum);
setX(x);
setY(y);
setZ(z);
}
/**
* Create a new earth-centred, earth-fixed reference from the given latitude
* and longitude.
*
* @param ll
* latitude and longitude.
* @since 1.1
*/
public ECEFRef(LatLng ll) {
super(ll.getDatum());
Ellipsoid ellipsoid = getDatum().getReferenceEllipsoid();
double phi = Math.toRadians(ll.getLatitude());
double lambda = Math.toRadians(ll.getLongitude());
double h = ll.getHeight();
double a = ellipsoid.getSemiMajorAxis();
double f = ellipsoid.getFlattening();
double eSquared = (2 * f) - (f * f);
double nphi = a / Math.sqrt(1 - eSquared * Util.sinSquared(phi));
setX((nphi + h) * Math.cos(phi) * Math.cos(lambda));
setY((nphi + h) * Math.cos(phi) * Math.sin(lambda));
setZ((nphi * (1 - eSquared) + h) * Math.sin(phi));
}
/**
* Convert this ECEFRef object to a LatLng object.
*
* @return the equivalent latitude and longitude.
* @since 1.1
*/
public LatLng toLatLng() {
Ellipsoid ellipsoid = getDatum().getReferenceEllipsoid();
double a = ellipsoid.getSemiMajorAxis();
double b = ellipsoid.getSemiMinorAxis();
double e2Squared = ((a * a) - (b * b)) / (b * b);
double f = ellipsoid.getFlattening();
double eSquared = (2 * f) - (f * f);
double p = Math.sqrt((x * x) + (y * y));
double theta = Math.atan((z * a) / (p * b));
double phi = Math.atan((z + (e2Squared * b * Util.sinCubed(theta)))
/ (p - eSquared * a * Util.cosCubed(theta)));
double lambda = Math.atan2(y, x);
double nphi = a / Math.sqrt(1 - eSquared * Util.sinSquared(phi));
double h = (p / Math.cos(phi)) - nphi;
return new LatLng(Math.toDegrees(phi), Math.toDegrees(lambda), h,
new uk.me.jstott.jcoord.datum.WGS84Datum());
}
/**
* Get the x co-ordinate.
*
* @return the x co-ordinate.
* @since 1.1
*/
public double getX() {
return x;
}
/**
* Get the y co-ordinate.
*
* @return the y co-ordinate.
* @since 1.1
*/
public double getY() {
return y;
}
/**
* Get the z co-ordinate.
*
* @return the z co-ordinate.
* @since 1.1
*/
public double getZ() {
return z;
}
/**
* Set the x co-ordinate.
*
* @param x
* the new x co-ordinate.
* @since 1.1
*/
public void setX(double x) {
this.x = x;
}
/**
* Set the y co-ordinate.
*
* @param y
* the y co-ordinate.
* @since 1.1
*/
public void setY(double y) {
this.y = y;
}
/**
* Set the z co-ordinate.
*
* @param z
* the z co-ordinate.
* @since 1.1
*/
public void setZ(double z) {
this.z = z;
}
/**
* Get a String representation of this ECEF reference.
*
* @return a String representation of this ECEF reference.
* @since 1.1
*/
public String toString() {
return "(" + x + "," + y + "," + z + ")";
}
}
================================================
FILE: src/uk/me/jstott/jcoord/IrishRef.java
================================================
package uk.me.jstott.jcoord;
import uk.me.jstott.jcoord.datum.Ireland1965Datum;
import uk.me.jstott.jcoord.ellipsoid.Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent an Irish National Grid reference.
*
*
*
* Irish National Grid
*
* Projection: Transverse Mercator
* Reference ellipsoid: Modified Airy
* Units: metres
* Origin: 53°30'N, 8°W
* False co-ordinates of origin: 200000m east, 250000m north
*
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-02-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class IrishRef extends CoordinateSystem {
/**
* The easting in metres relative to the origin of the British National Grid.
*/
private double easting;
/**
* The northing in metres relative to the origin of the British National Grid.
*/
private double northing;
public static final double SCALE_FACTOR = 1.000035;
public static final double FALSE_ORIGIN_LATITUDE = 53.5;
public static final double FALSE_ORIGIN_LONGITUDE = -8.0;
public static final double FALSE_ORIGIN_EASTING = 200000.0;
public static final double FALSE_ORIGIN_NORTHING = 250000.0;
/**
* Create a new Ordnance Survey grid reference using a given easting and
* northing. The easting and northing must be in metres and must be relative
* to the origin of the British National Grid.
*
* @param easting
* the easting in metres. Must be greater than or equal to 0.0 and
* less than 800000.0.
* @param northing
* the northing in metres. Must be greater than or equal to 0.0 and
* less than 1400000.0.
* @throws IllegalArgumentException
* if either the easting or the northing are invalid.
* @since 1.1
*/
public IrishRef(double easting, double northing) throws IllegalArgumentException {
super(Ireland1965Datum.getInstance());
setEasting(easting);
setNorthing(northing);
}
/**
* Take a string formatted as a six-figure OS grid reference (e.g. "TG514131")
* and create a new OSRef object that represents that grid reference. The
* first character must be H, N, S, O or T. The second character can be any
* uppercase character from A through Z excluding I.
*
* @param ref
* a String representing a six-figure Ordnance Survey grid reference
* in the form XY123456
* @throws IllegalArgumentException
* if ref is not of the form XY123456
* @since 1.1
*/
public IrishRef(String ref) throws IllegalArgumentException {
super(Ireland1965Datum.getInstance());
// if (ref.matches(""))
// TODO 2006-02-05 : check format
char ch = ref.charAt(0);
// Thanks to Nick Holloway for pointing out the radix bug here
int east = Integer.parseInt(ref.substring(1, 4)) * 100;
int north = Integer.parseInt(ref.substring(4, 7)) * 100;
if (ch > 73)
ch--; // Adjust for no I
double nx = ((ch - 65) % 5) * 100000;
double ny = (4 - Math.floor((ch - 65) / 5)) * 100000;
setEasting(east + nx);
setNorthing(north + ny);
}
/**
* Create an IrishRef object from the given latitude and longitude.
*
* @since 1.1
*/
public IrishRef(LatLng ll) {
super(Ireland1965Datum.getInstance());
Ellipsoid ellipsoid = getDatum().getReferenceEllipsoid();
double N0 = FALSE_ORIGIN_NORTHING;
double E0 = FALSE_ORIGIN_EASTING;
double phi0 = Math.toRadians(FALSE_ORIGIN_LATITUDE);
double lambda0 = Math.toRadians(FALSE_ORIGIN_LONGITUDE);
double a = ellipsoid.getSemiMajorAxis() * SCALE_FACTOR;
double b = ellipsoid.getSemiMinorAxis() * SCALE_FACTOR;
double eSquared = ellipsoid.getEccentricitySquared();
double phi = Math.toRadians(ll.getLatitude());
double lambda = Math.toRadians(ll.getLongitude());
double E = 0.0;
double N = 0.0;
double n = (a - b) / (a + b);
double v = a
* Math.pow(1.0 - eSquared * Util.sinSquared(phi), -0.5);
double rho = a * (1.0 - eSquared)
* Math.pow(1.0 - eSquared * Util.sinSquared(phi), -1.5);
double etaSquared = (v / rho) - 1.0;
double M = b
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phi - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.sin(phi - phi0) * Math.cos(phi + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.sin(2.0 * (phi - phi0)) * Math.cos(2.0 * (phi + phi0))) - (((35.0 / 24.0)
* n * n * n)
* Math.sin(3.0 * (phi - phi0)) * Math.cos(3.0 * (phi + phi0))));
double I = M + N0;
double II = (v / 2.0) * Math.sin(phi) * Math.cos(phi);
double III = (v / 24.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 3.0)
* (5.0 - Util.tanSquared(phi) + (9.0 * etaSquared));
double IIIA = (v / 720.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 5.0)
* (61.0 - (58.0 * Util.tanSquared(phi)) + Math.pow(Math.tan(phi), 4.0));
double IV = v * Math.cos(phi);
double V = (v / 6.0) * Math.pow(Math.cos(phi), 3.0)
* ((v / rho) - Util.tanSquared(phi));
double VI = (v / 120.0)
* Math.pow(Math.cos(phi), 5.0)
* (5.0 - (18.0 * Util.tanSquared(phi)) + (Math.pow(Math.tan(phi), 4.0))
+ (14 * etaSquared) - (58 * Util.tanSquared(phi) * etaSquared));
N = I + (II * Math.pow(lambda - lambda0, 2.0))
+ (III * Math.pow(lambda - lambda0, 4.0))
+ (IIIA * Math.pow(lambda - lambda0, 6.0));
E = E0 + (IV * (lambda - lambda0)) + (V * Math.pow(lambda - lambda0, 3.0))
+ (VI * Math.pow(lambda - lambda0, 5.0));
setEasting(E);
setNorthing(N);
}
/**
* Return a String representation of this Irish grid reference showing the
* easting and northing in metres.
*
* @return a String represenation of this Irish grid reference
* @since 1.1
*/
public String toString() {
return "(" + easting + ", " + northing + ")";
}
/**
* Return a String representation of this Irish grid reference using the
* six-figure notation in the form X123456
*
* @return a String representing this Irish grid reference in six-figure
* notation
* @since 1.0
*/
public String toSixFigureString() {
int hundredkmE = (int) Math.floor(easting / 100000);
int hundredkmN = (int) Math.floor(northing / 100000);
int charOffset = 4 - hundredkmN;
int index = 65 + (5 * charOffset) + hundredkmE;
if (index >= 73)
index++;
String letter = Character.toString((char) index);
int e = (int) Math.floor((easting - (100000 * hundredkmE)) / 100);
int n = (int) Math.floor((northing - (100000 * hundredkmN)) / 100);
String es = "" + e;
if (e < 100)
es = "0" + es;
if (e < 10)
es = "0" + es;
String ns = "" + n;
if (n < 100)
ns = "0" + ns;
if (n < 10)
ns = "0" + ns;
return letter + es + ns;
}
/**
* Convert this Irish grid reference to a latitude/longitude pair using the
* Ireland 1965 datum. Note that, the LatLng object may need to be converted to the
* WGS84 datum depending on the application.
*
* @return a LatLng object representing this Irish grid reference using the
* Ireland 1965 datum
* @since 1.1
*/
public LatLng toLatLng() {
double N0 = FALSE_ORIGIN_NORTHING;
double E0 = FALSE_ORIGIN_EASTING;
double phi0 = Math.toRadians(FALSE_ORIGIN_LATITUDE);
double lambda0 = Math.toRadians(FALSE_ORIGIN_LONGITUDE);
double a = getDatum().getReferenceEllipsoid().getSemiMajorAxis();
double b = getDatum().getReferenceEllipsoid().getSemiMinorAxis();
double eSquared = getDatum().getReferenceEllipsoid()
.getEccentricitySquared();
double phi = 0.0;
double lambda = 0.0;
double E = this.easting;
double N = this.northing;
double n = (a - b) / (a + b);
double M = 0.0;
double phiPrime = ((N - N0) / (a * SCALE_FACTOR)) + phi0;
do {
M = (b * SCALE_FACTOR)
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phiPrime - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.sin(phiPrime - phi0) * Math.cos(phiPrime + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.sin(2.0 * (phiPrime - phi0)) * Math
.cos(2.0 * (phiPrime + phi0))) - (((35.0 / 24.0) * n * n * n)
* Math.sin(3.0 * (phiPrime - phi0)) * Math
.cos(3.0 * (phiPrime + phi0))));
phiPrime += (N - N0 - M) / (a * SCALE_FACTOR);
} while ((N - N0 - M) >= 0.001);
double v = a * SCALE_FACTOR
* Math.pow(1.0 - eSquared * Util.sinSquared(phiPrime), -0.5);
double rho = a * SCALE_FACTOR * (1.0 - eSquared)
* Math.pow(1.0 - eSquared * Util.sinSquared(phiPrime), -1.5);
double etaSquared = (v / rho) - 1.0;
double VII = Math.tan(phiPrime) / (2 * rho * v);
double VIII = (Math.tan(phiPrime) / (24.0 * rho * Math.pow(v, 3.0)))
* (5.0 + (3.0 * Util.tanSquared(phiPrime)) + etaSquared - (9.0 * Util
.tanSquared(phiPrime) * etaSquared));
double IX = (Math.tan(phiPrime) / (720.0 * rho * Math.pow(v, 5.0)))
* (61.0 + (90.0 * Util.tanSquared(phiPrime)) + (45.0 * Util
.tanSquared(phiPrime) * Util.tanSquared(phiPrime)));
double X = Util.sec(phiPrime) / v;
double XI = (Util.sec(phiPrime) / (6.0 * v * v * v))
* ((v / rho) + (2 * Util.tanSquared(phiPrime)));
double XII = (Util.sec(phiPrime) / (120.0 * Math.pow(v, 5.0)))
* (5.0 + (28.0 * Util.tanSquared(phiPrime)) + (24.0 * Util
.tanSquared(phiPrime) * Util.tanSquared(phiPrime)));
double XIIA = (Util.sec(phiPrime) / (5040.0 * Math.pow(v, 7.0)))
* (61.0 + (662.0 * Util.tanSquared(phiPrime))
+ (1320.0 * Util.tanSquared(phiPrime) * Util.tanSquared(phiPrime)) + (720.0
* Util.tanSquared(phiPrime) * Util.tanSquared(phiPrime) * Util
.tanSquared(phiPrime)));
phi = phiPrime - (VII * Math.pow(E - E0, 2.0))
+ (VIII * Math.pow(E - E0, 4.0)) - (IX * Math.pow(E - E0, 6.0));
lambda = lambda0 + (X * (E - E0)) - (XI * Math.pow(E - E0, 3.0))
+ (XII * Math.pow(E - E0, 5.0)) - (XIIA * Math.pow(E - E0, 7.0));
return new LatLng(Math.toDegrees(phi), Math.toDegrees(lambda));
}
/**
* Get the easting in metres relative the origin of the British National Grid.
*
* @return the easting in metres.
* @since 1.1
*/
public double getEasting() {
return easting;
}
/**
* Get the northing in metres relative to the origin of the British National
* Grid.
*
* @return the northing in metres.
* @since 1.1
*/
public double getNorthing() {
return northing;
}
/**
* Set the easting for this OSRef.
*
* @param easting
* the easting in metres. Must be greater than or equal to 0.0 and
* less than 400000.0.
* @throws IllegalArgumentException
* if the easting is invalid.
* @since 1.1
*/
public void setEasting(double easting) throws IllegalArgumentException {
if (easting < 0.0 || easting >= 400000.0) {
throw new IllegalArgumentException("Easting (" + easting
+ ") is invalid. Must be greather than or equal to 0.0 and "
+ "less than 400000.0.");
}
this.easting = easting;
}
/**
* Set the northing for this OSRef
*
* @param northing
* the northing in metres. Must be greater than or equal to 0.0 and
* less than or equal to 500000.0.
* @throws IllegalArgumentException
* if either the northing is invalid.
* @since 1.1
*/
public void setNorthing(double northing) throws IllegalArgumentException {
if (northing < 0.0 || northing > 500000.0) {
throw new IllegalArgumentException("Northing (" + northing
+ ") is invalid. Must be greather than or equal to 0.0 and less "
+ "than or equal to 500000.0.");
}
this.northing = northing;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/LatLng.java
================================================
package uk.me.jstott.jcoord;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.datum.WGS84Datum;
import uk.me.jstott.jcoord.ellipsoid.Airy1830Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent a latitude/longitude pair based on a particular datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-02-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.0
*/
public class LatLng {
/**
* Latitude in degrees.
*/
private double latitude;
/**
* Longitude in degrees.
*/
private double longitude;
/**
* Height.
*/
private double height;
/**
* Datum of this reference.
*/
private Datum datum = new WGS84Datum();
/**
* Latitude is north of the equator.
*/
public static final int NORTH = 1;
/**
* Latitude is south of the equator.
*/
public static final int SOUTH = -1;
/**
* Longitude is east of the prime meridian.
*/
public static final int EAST = 1;
/**
* Longitude is west of the prime meridian.
*/
public static final int WEST = -1;
/**
* Create a new LatLng object to represent a latitude/longitude pair using the
* WGS84 datum.
*
* @param latitude
* the latitude in degrees. Must be between -90.0 and 90.0 inclusive.
* -90.0 and 90.0 are effectively equivalent.
* @param longitude
* the longitude in degrees. Must be between -180.0 and 180.0
* inclusive. -180.0 and 180.0 are effectively equivalent.
* @throws IllegalArgumentException
* if either the given latitude or the given longitude are invalid.
* @since 1.0
*/
public LatLng(double latitude, double longitude) {
this(latitude, longitude, 0, new WGS84Datum());
}
/**
* Create a new LatLng object to represent a latitude/longitude pair using the
* WGS84 datum.
*
* @param latitude
* the latitude in degrees. Must be between -90.0 and 90.0 inclusive.
* -90.0 and 90.0 are effectively equivalent.
* @param longitude
* the longitude in degrees. Must be between -180.0 and 180.0
* inclusive. -180.0 and 180.0 are effectively equivalent.
* @param height
* the perpendicular height above the reference ellipsoid.
* @throws IllegalArgumentException
* if either the given latitude or the given longitude are invalid.
* @since 1.1
*/
public LatLng(double latitude, double longitude, double height) {
this(latitude, longitude, height, new WGS84Datum());
}
/**
* Create a new LatLng object to represent a latitude/longitude pair using the
* WGS84 datum.
*
* @param latitudeDegrees
* the degrees part of the latitude. Must be 0 <= latitudeDegrees <=
* 90.0.
* @param latitudeMinutes
* the minutes part of the latitude. Must be 0 <= latitudeMinutes <
* 60.0.
* @param latitudeSeconds
* the seconds part of the latitude. Must be 0 <= latitudeSeconds <
* 60.0.
* @param northSouth
* whether the latitude is north or south of the equator. One of
* LatLng.NORTH or LatLng.SOUTH.
* @param longitudeDegrees
* the degrees part of the longitude. Must be 0 <= longitudeDegrees <=
* 90.0.
* @param longitudeMinutes
* the minutes part of the longitude. Must be 0 <= longitudeMinutes <
* 60.0.
* @param longitudeSeconds
* the seconds part of the longitude. Must be 0 <= longitudeSeconds <
* 60.0.
* @param eastWest
* whether the longitude is east or west of the prime meridian. One
* of LatLng.EAST or LatLng.WEST.
* @throws IllegalArgumentException
* if any of the parameters are invalid.
* @since 1.1
*/
public LatLng(int latitudeDegrees, int latitudeMinutes,
double latitudeSeconds, int northSouth, int longitudeDegrees,
int longitudeMinutes, double longitudeSeconds, int eastWest)
throws IllegalArgumentException {
this(latitudeDegrees, latitudeMinutes, latitudeSeconds, northSouth,
longitudeDegrees, longitudeMinutes, longitudeSeconds, eastWest, 0.0,
new WGS84Datum());
}
/**
* Create a new LatLng object to represent a latitude/longitude pair using the
* WGS84 datum.
*
* @param latitudeDegrees
* the degrees part of the latitude. Must be 0 <= latitudeDegrees <=
* 90.0.
* @param latitudeMinutes
* the minutes part of the latitude. Must be 0 <= latitudeMinutes <
* 60.0.
* @param latitudeSeconds
* the seconds part of the latitude. Must be 0 <= latitudeSeconds <
* 60.0.
* @param northSouth
* whether the latitude is north or south of the equator. One of
* LatLng.NORTH or LatLng.SOUTH.
* @param longitudeDegrees
* the degrees part of the longitude. Must be 0 <= longitudeDegrees <=
* 90.0.
* @param longitudeMinutes
* the minutes part of the longitude. Must be 0 <= longitudeMinutes <
* 60.0.
* @param longitudeSeconds
* the seconds part of the longitude. Must be 0 <= longitudeSeconds <
* 60.0.
* @param eastWest
* whether the longitude is east or west of the prime meridian. One
* of LatLng.EAST or LatLng.WEST.
* @param height
* the perpendicular height above the reference ellipsoid.
* @throws IllegalArgumentException
* if any of the parameters are invalid.
* @since 1.1
*/
public LatLng(int latitudeDegrees, int latitudeMinutes,
double latitudeSeconds, int northSouth, int longitudeDegrees,
int longitudeMinutes, double longitudeSeconds, int eastWest, double height)
throws IllegalArgumentException {
this(latitudeDegrees, latitudeMinutes, latitudeSeconds, northSouth,
longitudeDegrees, longitudeMinutes, longitudeSeconds, eastWest, height,
new WGS84Datum());
}
/**
* Create a new LatLng object to represent a latitude/longitude pair using the
* specified datum.
*
* @param latitudeDegrees
* the degrees part of the latitude. Must be 0 <= latitudeDegrees <=
* 90.0.
* @param latitudeMinutes
* the minutes part of the latitude. Must be 0 <= latitudeMinutes <
* 60.0.
* @param latitudeSeconds
* the seconds part of the latitude. Must be 0 <= latitudeSeconds <
* 60.0.
* @param northSouth
* whether the latitude is north or south of the equator. One of
* LatLng.NORTH or LatLng.SOUTH.
* @param longitudeDegrees
* the degrees part of the longitude. Must be 0 <= longitudeDegrees <=
* 90.0.
* @param longitudeMinutes
* the minutes part of the longitude. Must be 0 <= longitudeMinutes <
* 60.0.
* @param longitudeSeconds
* the seconds part of the longitude. Must be 0 <= longitudeSeconds <
* 60.0.
* @param eastWest
* whether the longitude is east or west of the prime meridian. One
* of LatLng.EAST or LatLng.WEST.
* @param height
* the perpendicular height above the reference ellipsoid.
* @param datum
* the datum that this reference is based on.
* @throws IllegalArgumentException
* if any of the parameters are invalid.
* @since 1.1
*/
public LatLng(int latitudeDegrees, int latitudeMinutes,
double latitudeSeconds, int northSouth, int longitudeDegrees,
int longitudeMinutes, double longitudeSeconds, int eastWest,
double height, Datum datum) throws IllegalArgumentException {
if (latitudeDegrees < 0.0 || latitudeDegrees > 90.0
|| latitudeMinutes < 0.0 || latitudeMinutes >= 60.0
|| latitudeSeconds < 0.0 || latitudeSeconds >= 60.0
|| (northSouth != SOUTH && northSouth != NORTH)) {
throw new IllegalArgumentException("Invalid latitude");
}
if (longitudeDegrees < 0.0 || longitudeDegrees > 90.0
|| longitudeMinutes < 0.0 || longitudeMinutes >= 60.0
|| longitudeSeconds < 0.0 || longitudeSeconds >= 60.0
|| (eastWest != SOUTH && eastWest != NORTH)) {
throw new IllegalArgumentException("Invalid longitude");
}
this.latitude = northSouth
* (latitudeDegrees + (latitudeMinutes / 60.0) + (latitudeSeconds / 3600.0));
this.longitude = eastWest
* (longitudeDegrees + (longitudeMinutes / 60.0) + (longitudeSeconds / 3600.0));
this.datum = datum;
}
/**
* Create a new LatLng object to represent a latitude/longitude pair using the
* specified datum.
*
* @param latitude
* the latitude in degrees. Must be between -90.0 and 90.0 inclusive.
* -90.0 and 90.0 are effectively equivalent.
* @param longitude
* the longitude in degrees. Must be between -180.0 and 180.0
* inclusive. -180.0 and 180.0 are effectively equivalent.
* @param height
* the perpendicular height above the reference ellipsoid.
* @param datum
* the datum that this reference is based on.
* @throws IllegalArgumentException
* if either the given latitude or the given longitude are invalid.
* @since 1.1
*/
public LatLng(double latitude, double longitude, double height, Datum datum)
throws IllegalArgumentException {
if (latitude < -90.0 || latitude > 90.0) {
throw new IllegalArgumentException("Latitude (" + latitude
+ ") is invalid. Must be between -90.0 and 90.0 inclusive.");
}
if (longitude < -180.0 || longitude > 180.0) {
throw new IllegalArgumentException("Longitude (" + longitude
+ ") is invalid. Must be between -180.0 and 180.0 inclusive.");
}
this.latitude = latitude;
this.longitude = longitude;
this.height = height;
this.datum = datum;
}
/**
* Get a String representation of this LatLng object.
*
* @return a String representation of this LatLng object.
* @since 1.0
*/
public String toString() {
return "(" + this.latitude + ", " + this.longitude + ")";
}
/**
* Return a String representation of this LatLng object in
* degrees-minutes-seconds format. The returned format will be like this: DD
* MM SS.SSS N DD MM SS.SSS E where DD is the number of degrees, MM is the
* number of minutes, SS.SSS is the number of seconds, N is either N or S to
* indicate north or south of the equator and E is either E or W to indicate
* east or west of the prime meridian.
*
* @return a String representation of this LatLng object in DMS format.
* @since 1.1
*/
public String toDMSString() {
String ret = formatLatitude() + " " + formatLongitude();
return ret;
}
/**
* Format the latitude into degrees-minutes-seconds format.
*
* @return the formatted String
* @since 1.1
*/
private String formatLatitude() {
String ns = getLatitude() >= 0 ? "N" : "S";
return Math.abs(getLatitudeDegrees()) + " " + getLatitudeMinutes() + " "
+ getLatitudeSeconds() + " " + ns;
}
/**
* Format the longitude into degrees-minutes-seconds format.
*
* @return the formatted String.
* @since 1.1
*/
private String formatLongitude() {
String ew = getLongitude() >= 0 ? "E" : "W";
return Math.abs(getLongitudeDegrees()) + " " + getLongitudeMinutes() + " "
+ getLongitudeSeconds() + " " + ew;
}
/**
* Convert this latitude and longitude into an OSGB (Ordnance Survey of Great
* Britain) grid reference.
*
* @return the converted OSGB grid reference.
* @since 1.0
*/
public OSRef toOSRef() {
Airy1830Ellipsoid airy1830 = Airy1830Ellipsoid.getInstance();
double OSGB_F0 = 0.9996012717;
double N0 = -100000.0;
double E0 = 400000.0;
double phi0 = Math.toRadians(49.0);
double lambda0 = Math.toRadians(-2.0);
double a = airy1830.getSemiMajorAxis();
double b = airy1830.getSemiMinorAxis();
double eSquared = airy1830.getEccentricitySquared();
double phi = Math.toRadians(getLat());
double lambda = Math.toRadians(getLng());
double E = 0.0;
double N = 0.0;
double n = (a - b) / (a + b);
double v = a * OSGB_F0
* Math.pow(1.0 - eSquared * Util.sinSquared(phi), -0.5);
double rho = a * OSGB_F0 * (1.0 - eSquared)
* Math.pow(1.0 - eSquared * Util.sinSquared(phi), -1.5);
double etaSquared = (v / rho) - 1.0;
double M = (b * OSGB_F0)
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phi - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.sin(phi - phi0) * Math.cos(phi + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.sin(2.0 * (phi - phi0)) * Math.cos(2.0 * (phi + phi0))) - (((35.0 / 24.0)
* n * n * n)
* Math.sin(3.0 * (phi - phi0)) * Math.cos(3.0 * (phi + phi0))));
double I = M + N0;
double II = (v / 2.0) * Math.sin(phi) * Math.cos(phi);
double III = (v / 24.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 3.0)
* (5.0 - Util.tanSquared(phi) + (9.0 * etaSquared));
double IIIA = (v / 720.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 5.0)
* (61.0 - (58.0 * Util.tanSquared(phi)) + Math.pow(Math.tan(phi), 4.0));
double IV = v * Math.cos(phi);
double V = (v / 6.0) * Math.pow(Math.cos(phi), 3.0)
* ((v / rho) - Util.tanSquared(phi));
double VI = (v / 120.0)
* Math.pow(Math.cos(phi), 5.0)
* (5.0 - (18.0 * Util.tanSquared(phi)) + (Math.pow(Math.tan(phi), 4.0))
+ (14 * etaSquared) - (58 * Util.tanSquared(phi) * etaSquared));
N = I + (II * Math.pow(lambda - lambda0, 2.0))
+ (III * Math.pow(lambda - lambda0, 4.0))
+ (IIIA * Math.pow(lambda - lambda0, 6.0));
E = E0 + (IV * (lambda - lambda0)) + (V * Math.pow(lambda - lambda0, 3.0))
+ (VI * Math.pow(lambda - lambda0, 5.0));
return new OSRef(E, N);
}
/**
* Convert this latitude and longitude to a UTM reference.
*
* @return the converted UTM reference.
* @throws NotDefinedOnUTMGridException
* if an attempt is made to convert a LatLng that falls outside the
* area covered by the UTM grid. The UTM grid is only defined for
* latitudes south of 84°N and north of 80°S.
* @since 1.0
*/
public UTMRef toUTMRef() throws NotDefinedOnUTMGridException {
if (getLatitude() < -80 || getLatitude() > 84) {
throw new NotDefinedOnUTMGridException("Latitude (" + getLatitude()
+ ") falls outside the UTM grid.");
}
if (this.longitude == 180.0) {
this.longitude = -180.0;
}
double UTM_F0 = 0.9996;
double a = uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid.getInstance().getSemiMajorAxis();
double eSquared = uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid.getInstance().getEccentricitySquared();
double longitude = this.longitude;
double latitude = this.latitude;
double latitudeRad = latitude * (Math.PI / 180.0);
double longitudeRad = longitude * (Math.PI / 180.0);
int longitudeZone = (int) Math.floor((longitude + 180.0) / 6.0) + 1;
// Special zone for Norway
if (latitude >= 56.0 && latitude < 64.0 && longitude >= 3.0
&& longitude < 12.0) {
longitudeZone = 32;
}
// Special zones for Svalbard
if (latitude >= 72.0 && latitude < 84.0) {
if (longitude >= 0.0 && longitude < 9.0) {
longitudeZone = 31;
} else if (longitude >= 9.0 && longitude < 21.0) {
longitudeZone = 33;
} else if (longitude >= 21.0 && longitude < 33.0) {
longitudeZone = 35;
} else if (longitude >= 33.0 && longitude < 42.0) {
longitudeZone = 37;
}
}
double longitudeOrigin = (longitudeZone - 1) * 6 - 180 + 3;
double longitudeOriginRad = longitudeOrigin * (Math.PI / 180.0);
char UTMZone = UTMRef.getUTMLatitudeZoneLetter(latitude);
double ePrimeSquared = (eSquared) / (1 - eSquared);
double n = a
/ Math.sqrt(1 - eSquared * Math.sin(latitudeRad)
* Math.sin(latitudeRad));
double t = Math.tan(latitudeRad) * Math.tan(latitudeRad);
double c = ePrimeSquared * Math.cos(latitudeRad) * Math.cos(latitudeRad);
double A = Math.cos(latitudeRad) * (longitudeRad - longitudeOriginRad);
double M = a
* ((1 - eSquared / 4 - 3 * eSquared * eSquared / 64 - 5 * eSquared
* eSquared * eSquared / 256)
* latitudeRad
- (3 * eSquared / 8 + 3 * eSquared * eSquared / 32 + 45 * eSquared
* eSquared * eSquared / 1024)
* Math.sin(2 * latitudeRad)
+ (15 * eSquared * eSquared / 256 + 45 * eSquared * eSquared
* eSquared / 1024) * Math.sin(4 * latitudeRad) - (35 * eSquared
* eSquared * eSquared / 3072)
* Math.sin(6 * latitudeRad));
double UTMEasting = (UTM_F0
* n
* (A + (1 - t + c) * Math.pow(A, 3.0) / 6 + (5 - 18 * t + t * t + 72
* c - 58 * ePrimeSquared)
* Math.pow(A, 5.0) / 120) + 500000.0);
double UTMNorthing = (UTM_F0 * (M + n
* Math.tan(latitudeRad)
* (A * A / 2 + (5 - t + (9 * c) + (4 * c * c)) * Math.pow(A, 4.0) / 24 + (61
- (58 * t) + (t * t) + (600 * c) - (330 * ePrimeSquared))
* Math.pow(A, 6.0) / 720)));
// Adjust for the southern hemisphere
if (latitude < 0) {
UTMNorthing += 10000000.0;
}
return new UTMRef(longitudeZone, UTMZone, UTMEasting, UTMNorthing);
}
/**
* Convert this latitude and longitude to an MGRS reference.
*
* @return the converted MGRS reference
* @since 1.1
*/
public MGRSRef toMGRSRef() {
UTMRef utm = toUTMRef();
return new MGRSRef(utm);
}
/**
* Convert this LatLng from the OSGB36 datum to the WGS84 datum using an
* approximate Helmert transformation.
*
* @since 1.0
*/
public void toWGS84() {
double a = Airy1830Ellipsoid.getInstance().getSemiMajorAxis();
double eSquared = Airy1830Ellipsoid.getInstance().getEccentricitySquared();
double phi = Math.toRadians(latitude);
double lambda = Math.toRadians(longitude);
double v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phi)));
double H = 0; // height
double x = (v + H) * Math.cos(phi) * Math.cos(lambda);
double y = (v + H) * Math.cos(phi) * Math.sin(lambda);
double z = ((1 - eSquared) * v + H) * Math.sin(phi);
double tx = 446.448;
// ty : Incorrect value in v1.0 (-124.157). Corrected in v1.1.
double ty = -125.157;
double tz = 542.060;
double s = -0.0000204894;
double rx = Math.toRadians(0.00004172222);
double ry = Math.toRadians(0.00006861111);
double rz = Math.toRadians(0.00023391666);
double xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z);
double yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z);
double zB = tz + (-ry * x) + (rx * y) + (z * (1 + s));
a = uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid.getInstance().getSemiMajorAxis();
eSquared = uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid.getInstance()
.getEccentricitySquared();
double lambdaB = Math.toDegrees(Math.atan(yB / xB));
double p = Math.sqrt((xB * xB) + (yB * yB));
double phiN = Math.atan(zB / (p * (1 - eSquared)));
for (int i = 1; i < 10; i++) {
v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phiN)));
double phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p);
phiN = phiN1;
}
double phiB = Math.toDegrees(phiN);
latitude = phiB;
longitude = lambdaB;
}
/**
*
*
* @param d
* @since 1.1
*/
public void toDatum(Datum d) {
double invert = 1;
if (!(datum instanceof WGS84Datum) && !(d instanceof WGS84Datum)) {
toDatum(new WGS84Datum());
} else {
if (d instanceof WGS84Datum) {
// Don't do anything if datum and d are both WGS84.
return;
}
invert = -1;
}
double a = datum.getReferenceEllipsoid().getSemiMajorAxis();
double eSquared = datum.getReferenceEllipsoid().getEccentricitySquared();
double phi = Math.toRadians(latitude);
double lambda = Math.toRadians(longitude);
double v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phi)));
double H = height; // height
double x = (v + H) * Math.cos(phi) * Math.cos(lambda);
double y = (v + H) * Math.cos(phi) * Math.sin(lambda);
double z = ((1 - eSquared) * v + H) * Math.sin(phi);
double dx = invert * d.getDx();// 446.448;
double dy = invert * d.getDy();// -125.157;
double dz = invert * d.getDz();// 542.060;
double ds = invert * d.getDs() / 1000000.0;// -0.0000204894;
double rx = invert * Math.toRadians(d.getRx() / 3600.0);// Math.toRadians(0.00004172222);
double ry = invert * Math.toRadians(d.getRy() / 3600.0);// Math.toRadians(0.00006861111);
double rz = invert * Math.toRadians(d.getRz() / 3600.0);// Math.toRadians(0.00023391666);
double sc = 1 + ds;
double xB = dx + (x * sc) + ((-rx * y) * sc) + ((ry * z) * sc);
double yB = dy + ((rz * x) * sc) + (y * sc) + ((-rx * z) * sc);
double zB = dz + ((-ry * x) * sc) + ((rx * y) * sc) + (z * sc);
a = d.getReferenceEllipsoid().getSemiMajorAxis();
eSquared = d.getReferenceEllipsoid().getEccentricitySquared();
double lambdaB = Math.toDegrees(Math.atan(yB / xB));
double p = Math.sqrt((xB * xB) + (yB * yB));
double phiN = Math.atan(zB / (p * (1 - eSquared)));
for (int i = 1; i < 10; i++) {
v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phiN)));
double phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p);
phiN = phiN1;
}
double phiB = Math.toDegrees(phiN);
latitude = phiB;
longitude = lambdaB;
}
/**
* Convert this LatLng from the WGS84 datum to the OSGB36 datum using an
* approximate Helmert transformation.
*
* @since 1.0
*/
public void toOSGB36() {
uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid wgs84 = uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid
.getInstance();
double a = wgs84.getSemiMajorAxis();
double eSquared = wgs84.getEccentricitySquared();
double phi = Math.toRadians(this.latitude);
double lambda = Math.toRadians(this.longitude);
double v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phi)));
double H = 0; // height
double x = (v + H) * Math.cos(phi) * Math.cos(lambda);
double y = (v + H) * Math.cos(phi) * Math.sin(lambda);
double z = ((1 - eSquared) * v + H) * Math.sin(phi);
double tx = -446.448;
// ty : Incorrect value in v1.0 (124.157). Corrected in v1.1.
double ty = 125.157;
double tz = -542.060;
double s = 0.0000204894;
double rx = Math.toRadians(-0.00004172222);
double ry = Math.toRadians(-0.00006861111);
double rz = Math.toRadians(-0.00023391666);
double xB = tx + (x * (1 + s)) + (-rx * y) + (ry * z);
double yB = ty + (rz * x) + (y * (1 + s)) + (-rx * z);
double zB = tz + (-ry * x) + (rx * y) + (z * (1 + s));
a = Airy1830Ellipsoid.getInstance().getSemiMajorAxis();
eSquared = Airy1830Ellipsoid.getInstance().getEccentricitySquared();
double lambdaB = Math.toDegrees(Math.atan(yB / xB));
double p = Math.sqrt((xB * xB) + (yB * yB));
double phiN = Math.atan(zB / (p * (1 - eSquared)));
for (int i = 1; i < 10; i++) {
v = a / (Math.sqrt(1 - eSquared * Util.sinSquared(phiN)));
double phiN1 = Math.atan((zB + (eSquared * v * Math.sin(phiN))) / p);
phiN = phiN1;
}
double phiB = Math.toDegrees(phiN);
latitude = phiB;
longitude = lambdaB;
}
/**
* Calculate the surface distance in kilometres from this LatLng to the given
* LatLng.
*
* @param ll
* the LatLng object to measure the distance to.
* @return the surface distance in kilometres.
* @since 1.0
*/
public double distance(LatLng ll) {
double er = 6366.707;
double latFrom = Math.toRadians(getLat());
double latTo = Math.toRadians(ll.getLat());
double lngFrom = Math.toRadians(getLng());
double lngTo = Math.toRadians(ll.getLng());
double d = Math.acos(Math.sin(latFrom) * Math.sin(latTo)
+ Math.cos(latFrom) * Math.cos(latTo) * Math.cos(lngTo - lngFrom))
* er;
return d;
}
/**
* Calculate the surface distance in miles from this LatLng to the given
* LatLng.
*
* @param ll
* the LatLng object to measure the distance to.
* @return the surface distance in miles.
* @since 1.1
*/
public double distanceMiles(LatLng ll) {
return distance(ll) / 1.609344;
}
/**
* Return the latitude in degrees.
*
* @return the latitude in degrees.
* @since 1.0
* @deprecated Use {@link #getLatitude() getLatitude()} instead.
*/
public double getLat() {
return latitude;
}
/**
* Return the latitude in degrees.
*
* @return the latitude in degrees.
* @since 1.1
*/
public double getLatitude() {
return latitude;
}
/**
*
*
* @return
* @since 1.1
*/
public int getLatitudeDegrees() {
double ll = getLatitude();
int deg = (int) Math.floor(ll);
double minx = ll - deg;
if (ll < 0 && minx != 0.0) {
deg++;
}
return deg;
}
/**
*
*
* @return
* @since 1.1
*/
public int getLatitudeMinutes() {
double ll = getLatitude();
int deg = (int) Math.floor(ll);
double minx = ll - deg;
if (ll < 0 && minx != 0.0) {
minx = 1 - minx;
}
int min = (int) Math.floor(minx * 60);
return min;
}
/**
*
*
* @return
* @since 1.1
*/
public double getLatitudeSeconds() {
double ll = getLatitude();
int deg = (int) Math.floor(ll);
double minx = ll - deg;
if (ll < 0 && minx != 0.0) {
minx = 1 - minx;
}
int min = (int) Math.floor(minx * 60);
double sec = ((minx * 60) - min) * 60;
return sec;
}
/**
* Return the longitude in degrees.
*
* @return the longitude in degrees.
* @since 1.0
* @deprecated Use {@link #getLongitude() getLongitude()} instead.
*/
public double getLng() {
return longitude;
}
/**
* Return the longitude in degrees.
*
* @return the longitude in degrees.
* @since 1.0
*/
public double getLongitude() {
return longitude;
}
/**
*
*
* @return
* @since 1.1
*/
public int getLongitudeDegrees() {
double ll = getLongitude();
int deg = (int) Math.floor(ll);
double minx = ll - deg;
if (ll < 0 && minx != 0.0) {
deg++;
}
return deg;
}
/**
*
*
* @return
* @since 1.1
*/
public int getLongitudeMinutes() {
double ll = getLongitude();
int deg = (int) Math.floor(ll);
double minx = ll - deg;
if (ll < 0 && minx != 0.0) {
minx = 1 - minx;
}
int min = (int) Math.floor(minx * 60);
return min;
}
/**
*
*
* @return
* @since 1.1
*/
public double getLongitudeSeconds() {
double ll = getLongitude();
int deg = (int) Math.floor(ll);
double minx = ll - deg;
if (ll < 0 && minx != 0.0) {
minx = 1 - minx;
}
int min = (int) Math.floor(minx * 60);
double sec = ((minx * 60) - min) * 60;
return sec;
}
/**
* Get the height.
*
* @return the height.
* @since 1.1
*/
public double getHeight() {
return height;
}
/**
* Get the datum.
*
* @return the datum.
* @since 1.1
*/
public Datum getDatum() {
return datum;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/MGRSRef.java
================================================
package uk.me.jstott.jcoord;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import uk.me.jstott.jcoord.datum.WGS84Datum;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent a Military Grid Reference System (MGRS) reference.
*
*
*
*
Military Grid Reference System (MGRS)
*
*
*
* The Military Grid Reference System (MGRS) is an extension of the Universal
* Transverse Mercator (UTM) reference system. An MGRS reference is made from 5
* parts:
*
*
* UTM Longitude Zone
*
*
* This is a number indicating which UTM longitude zone the reference falls
* into. Zones are numbered from 1 (starting at 180°W) through 60. Each zone
* is 6° wide.
*
*
* UTM Latitude Zone
*
*
* Latitude is split into regions that are 8° high, starting at 80°S.
* Latitude zones are lettered using C through X, but omitting I and O as they
* can easily be confused with the numbers 1 and 0.
*
*
* 100,000m Square identification
*
*
* Each UTM zone is treated as a square 100,000m to a side. The 50,000m easting
* is centred on the centre-point of the UTM zone. 100,000m squares are
* identified using two characters - one to identify the row and one to identify
* the column.
*
*
*
* Row identifiers use the characters A through V (omitting I and O again). The
* sequence is repeated every 2,000,000m from the equator. If the UTM longitude
* zone is odd, then the lettering is advanced by five characters to start at F.
*
*
*
* Column identifiers use the characters A through Z (again omitting I and O).
*
*
* Easting and northing
*
*
* Each 100,000m grid square is further divided into smaller squares
* representing 1m, 10m, 100m, 1,000m and 10,000m precision. The easting and
* northing are given using the numeric row and column reference of the square,
* starting at the bottom-left corner of the square.
*
*
* MGRS Reference Example
*
*
* 18SUU8362601432 is an example of an MGRS reference. '18' is the UTM longitude
* zone, 'S' is the UTM latitude zone, 'UU' is the 100,000m square
* identification, 83626 is the easting reference to 1m precision and 01432 is
* the northing reference to 1m precision.
*
*
* MGRSRef
*
*
* Methods are provided to query an MGRSRef object for its
* parameters. As MGRS references are related to UTM references, a
* {@link MGRSRef#toUTMRef() toUTMRef()} method is provided to
* convert an MGRSRef object into a {@link UTMRef}
* object. The reverse conversion can be made using the
* {@link #MGRSRef(UTMRef) MGRSRef(UTMRef)} constructor.
*
*
*
* MGRSRef objects can be converted to
* {@link LatLng LatLng} objects using the
* {@link MGRSRef#toLatLng() toLatLng()} method. The reverse
* conversion is made using the
* {@link LatLng#toMGRSRef() LatLng.toMGRSRef()} method.
*
*
*
* Some MGRS references use the Bessel 1841 ellipsoid rather than the Geodetic
* Reference System 1980 (GRS 1980), International or World Geodetic System 1984
* (WGS84) ellipsoids. Use the constructors with the optional boolean parameter
* to be able to specify whether your MGRS reference uses the Bessel 1841
* ellipsoid. Note that no automatic determination of the correct ellipsoid to
* use is made.
*
*
*
* Important note : There is currently no support for MGRS references in
* polar regions north of 84°N and south of 80°S. There is also no
* account made for UTM zones with slightly different sizes to normal around
* Svalbard and Norway.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 25-Feb-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class MGRSRef extends CoordinateSystem {
/**
*
*/
private int utmZoneNumber;
/**
*
*/
private char utmZoneChar;
/**
*
*/
private char eastingID;
/**
*
*/
private char northingID;
/**
*
*/
private int easting;
/**
*
*/
private int northing;
/**
* The initial precision of this MGRS reference. Must be one of
* MGRSRef.PRECISION_1M, MGRSRef.PRECISION_10M, MGRSRef.PRECISION_100M,
* MGRSRef.PRECISION_1000M or MGRSRef.PRECISION_10000M.
*/
private int precision;
/**
*
*/
private boolean isBessel;
/**
* Used to indicate a required precision of 10000m (10km).
*/
public static final int PRECISION_10000M = 10000;
/**
* Used to indicate a required precision of 1000m (1km).
*/
public static final int PRECISION_1000M = 1000;
/**
* Used to indicate a required precision of 100m.
*/
public static final int PRECISION_100M = 100;
/**
* Used to indicate a required precision of 10m.
*/
public static final int PRECISION_10M = 10;
/**
* Used to indicate a required precision of 1m.
*/
public static final int PRECISION_1M = 1;
/**
* Northing characters
*/
private static final char[] northingIDs =
new char[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M',
'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V' };
/**
* Create a new MGRS reference object from the given UTM reference. It is
* assumed that the UTMRef object is valid.
*
* @param utm
* a UTM reference.
* @since 1.1
*/
public MGRSRef(UTMRef utm) {
this(utm, false);
}
/**
* Create a new MGRS reference object from the given UTM reference. It is
* assumed that this MGRS reference represents a point using the GRS 1980,
* International or WGS84 ellipsoids. It is assumed that the UTMRef object is
* valid.
*
* @param utm
* a UTM reference.
* @param isBessel
* true if the parameters represent an MGRS reference using the
* Bessel 1841 ellipsoid; false is the parameters represent an MGRS
* reference using the GRS 1980, International or WGS84 ellipsoids.
* @since 1.1
*/
public MGRSRef(UTMRef utm, boolean isBessel) {
super(WGS84Datum.getInstance());
int lngZone = utm.getLngZone();
int set = ((lngZone - 1) % 6) + 1;
int eID =
(int) Math.floor(utm.getEasting() / 100000.0) + (8 * ((set - 1) % 3));
int nID = (int) Math.floor((utm.getNorthing() % 2000000) / 100000.0);
if (eID > 8)
eID++; // Offset for no I character
if (eID > 14)
eID++; // Offset for no O character
char eIDc = (char) (eID + 64);
// Northing ID offset for sets 2, 4 and 6
if (set % 2 == 0) {
nID += 5;
}
if (isBessel) {
nID += 10;
}
if (nID > 19) {
nID -= 20;
}
char nIDc = northingIDs[nID];
this.utmZoneNumber = lngZone;
this.utmZoneChar = utm.getLatZone();
this.eastingID = eIDc;
this.northingID = nIDc;
this.easting = (int) Math.round(utm.getEasting()) % 100000;
this.northing = (int) Math.round(utm.getNorthing()) % 100000;
this.precision = PRECISION_1M;
this.isBessel = isBessel;
}
/**
* Create a new MGRS reference object from the given paramaters. It is assumed
* that this MGRS reference represents a point using the GRS 1980,
* International or WGS84 ellipsoids. An IllegalArgumentException is thrown if
* any of the parameters are invalid.
*
* @param utmZoneNumber
* the UTM zone number representing the longitude.
* @param utmZoneChar
* the UTM zone character representing the latitude.
* @param eastingID
* character representing the 100,000km easting square.
* @param northingID
* character representing the 100,000km easting square.
* @param easting
* easting in metres.
* @param northing
* northing in metres.
* @param precision
* the precision of the given easting and northing. Must be one of
* MGRSRef.PRECISION_1M, MGRSRef.PRECISION_10M,
* MGRSRef.PRECISION_100M, MGRSRef.PRECISION_1000M or
* MGRSRef.PRECISION_10000M.
* @throws IllegalArgumentException
* if any of the given parameters are invalid.
* @since 1.1
*/
public MGRSRef(int utmZoneNumber, char utmZoneChar, char eastingID,
char northingID, int easting, int northing, int precision)
throws IllegalArgumentException {
this(utmZoneNumber, utmZoneChar, eastingID, northingID, easting, northing,
precision, false);
}
/**
* Create a new MGRS reference object from the given paramaters. An
* IllegalArgumentException is thrown if any of the parameters are invalid.
*
* @param utmZoneNumber
* the UTM zone number representing the longitude.
* @param utmZoneChar
* the UTM zone character representing the latitude.
* @param eastingID
* character representing the 100,000km easting square.
* @param northingID
* character representing the 100,000km easting square.
* @param easting
* easting in metres.
* @param northing
* northing in metres.
* @param precision
* the precision of the given easting and northing. Must be one of
* MGRSRef.PRECISION_1M, MGRSRef.PRECISION_10M,
* MGRSRef.PRECISION_100M, MGRSRef.PRECISION_1000M or
* MGRSRef.PRECISION_10000M.
* @param isBessel
* true if the parameters represent an MGRS reference using the
* Bessel 1841 ellipsoid; false is the parameters represent an MGRS
* reference using the GRS 1980, International or WGS84 ellipsoids.
* @throws IllegalArgumentException
* if any of the given parameters are invalid. Note that the
* parameters are only checked for the range of values that they can
* take on. Being able to create an MGRSRef object does not
* necessarily imply that the reference is valid.
* @since 1.1
*/
public MGRSRef(int utmZoneNumber, char utmZoneChar, char eastingID,
char northingID, int easting, int northing, int precision,
boolean isBessel) throws IllegalArgumentException {
super(WGS84Datum.getInstance());
if (utmZoneNumber < 1 || utmZoneNumber > 60) {
throw new IllegalArgumentException("Invalid utmZoneNumber ("
+ utmZoneNumber + ")");
}
if (utmZoneChar < 'A' || utmZoneChar > 'Z') {
throw new IllegalArgumentException("Invalid utmZoneChar (" + utmZoneChar
+ ")");
}
if (eastingID < 'A' || eastingID > 'Z' || eastingID == 'I'
|| eastingID == 'O') {
throw new IllegalArgumentException("Invalid eastingID (" + eastingID
+ ")");
}
if (northingID < 'A' || northingID > 'Z' || northingID == 'I'
|| northingID == 'O') {
throw new IllegalArgumentException("Invalid northingID (" + northingID
+ ")");
}
if (easting < 0 || easting > 99999) {
throw new IllegalArgumentException("Invalid easting (" + easting + ")");
}
if (northing < 0 || northing > 99999) {
throw new IllegalArgumentException("Invalid northing (" + northing + ")");
}
if (precision != PRECISION_1M && precision != PRECISION_10M
&& precision != PRECISION_100M && precision != PRECISION_1000M
&& precision != PRECISION_10000M) {
throw new IllegalArgumentException("Invalid precision (" + precision
+ ")");
}
this.utmZoneNumber = utmZoneNumber;
this.utmZoneChar = utmZoneChar;
this.eastingID = eastingID;
this.northingID = northingID;
this.easting = easting;
this.northing = northing;
this.precision = precision;
this.isBessel = isBessel;
}
/**
* Create a new MGRS reference object from the given String. Must be correctly
* formatted otherwise an IllegalArgumentException will be thrown. It is
* assumed that this MGRS reference represents a point using the GRS 1980,
* International or WGS84 ellipsoids.
*
* @param ref
* a String to create an MGRS reference from.
* @throws IllegalArgumentException
* if the given String is not correctly. formatted
* @since 1.1
*/
public MGRSRef(String ref) throws IllegalArgumentException {
this(ref, false);
}
/**
* Create a new MGRS reference object from the given String. Must be correctly
* formatted otherwise an IllegalArgumentException will be thrown.
*
* @param ref
* a String to create an MGRS reference from.
* @param isBessel
* true if the parameters represent an MGRS reference using the
* Bessel 1841 ellipsoid; false is the parameters represent an MGRS
* reference using the GRS 1980, International or WGS84 ellipsoids.
* @throws IllegalArgumentException
* if the given String is not correctly. formatted
* @since 1.1
*/
public MGRSRef(String ref, boolean isBessel) throws IllegalArgumentException {
super(WGS84Datum.getInstance());
Pattern p =
Pattern
.compile("(\\d{1,2})([C-X&&[^IO]])([A-Z&&[^IO]])([A-Z&&[^IO]])(\\d{2,10})");
Matcher m = p.matcher(ref);
if (!m.matches()) {
throw new IllegalArgumentException("Invalid MGRS reference (" + ref + ")");
}
this.utmZoneNumber = Integer.parseInt(m.group(1));
this.utmZoneChar = m.group(2).charAt(0);
this.eastingID = m.group(3).charAt(0);
this.northingID = m.group(4).charAt(0);
String en = m.group(5);
int enl = en.length();
if (enl % 2 != 0) {
throw new IllegalArgumentException("Invalid MGRS reference (" + ref + ")");
} else {
this.precision = (int) Math.pow(10, 5 - (enl / 2));
this.easting =
Integer.parseInt(en.substring(0, enl / 2)) * this.precision;
this.northing = Integer.parseInt(en.substring(enl / 2)) * this.precision;
}
}
/**
* Convert this MGRS reference to an equivelent UTM reference.
*
* @return the equivalent UTM reference.
* @since 1.1
*/
public UTMRef toUTMRef() {
int set = ((utmZoneNumber - 1) % 6) + 1;
int e = (int) eastingID - 65;
if (e > 15)
e--;
if (e > 9)
e--;
int ex = (easting + ((e % 8 + 1) * 100000)) % 1000000;
// TODO: take account of Bessel ellipsoid
// TODO: take account of odd zone sizes near Norway and Svalbard
int n = (int) northingID - 64;
if (n > 15)
n--;
if (n > 9)
n--;
if ((set % 2) == 0)
n -= 5;
if (n < 0)
n += 16;
int nx = 0;
boolean isOffset = ((set % 2) == 0);
switch (utmZoneChar) {
case 'Q':
if ((!isOffset && northingID < 'T')
|| (isOffset && (northingID < 'C' || northingID > 'E'))) {
nx += 2000000;
}
break;
case 'R':
nx += 2000000;
break;
case 'S':
if ((!isOffset && northingID < 'R') || (isOffset && (northingID > 'E'))) {
nx += 4000000;
} else {
nx += 2000000;
}
break;
case 'T':
nx += 4000000;
break;
case 'U':
if ((!isOffset && northingID < 'P') || (isOffset && (northingID < 'U'))) {
nx += 6000000;
} else {
nx += 4000000;
}
break;
case 'V':
case 'W':
nx += 6000000;
break;
case 'X':
if (true) {
nx += 8000000;
} else {
nx += 6000000;
}
}
nx += (100000 * (n - 1)) + northing;
return new UTMRef(utmZoneNumber, utmZoneChar, (double) ex, (double) nx);
}
/**
* Convert this MGRS reference to a latitude and longitude.
*
* @return the converted latitude and longitude.
* @since 1.1
*/
public LatLng toLatLng() {
return toUTMRef().toLatLng();
}
/**
* Return a String representation of this MGRS Reference to whatever precision
* this reference is set to.
*
* @return a String representation of this MGRS reference to whatever
* precision this reference is set to.
* @since 1.1
*/
public String toString() {
return toString(precision);
}
/**
* Return a String representation of this MGRS reference to 1m, 10m, 100m,
* 1000m or 10000m precision.
*
* @param precision
* One of MGRSRef.PRECISION_1M, MGRSRef.PRECISION_10M,
* MGRSRef.PRECISION_100M, MGRSRef.PRECISION_1000M,
* MGRSRef.PRECISION_10000M.
* @return a String representation of this MGRS reference to the required
* precision.
* @since 1.1
*/
public String toString(int precision) {
if (precision != PRECISION_1M && precision != PRECISION_10M
&& precision != PRECISION_100M && precision != PRECISION_1000M
&& precision != PRECISION_10000M) {
throw new IllegalArgumentException("Precision (" + precision
+ ") must be 1m, 10m, 100m, 1000m or 10000m");
}
int eastingR = (int) Math.floor(easting / precision);
int northingR = (int) Math.floor(northing / precision);
int padding = 5;
switch (precision) {
case PRECISION_10M:
padding = 4;
break;
case PRECISION_100M:
padding = 3;
break;
case PRECISION_1000M:
padding = 2;
break;
case PRECISION_10000M:
padding = 1;
break;
}
String eastingRs = Integer.toString(eastingR);
int ez = padding - eastingRs.length();
while (ez > 0) {
eastingRs = "0" + eastingRs;
ez--;
}
String northingRs = Integer.toString(northingR);
int nz = padding - northingRs.length();
while (nz > 0) {
northingRs = "0" + northingRs;
nz--;
}
String utmZonePadding = "";
if (utmZoneNumber < 10) {
utmZonePadding = "0";
}
return utmZonePadding + utmZoneNumber + Character.toString(utmZoneChar)
+ Character.toString(eastingID) + Character.toString(northingID)
+ eastingRs + northingRs;
}
/**
*
*
* @return the easting
* @since 1.1
*/
public int getEasting() {
return easting;
}
/**
*
*
* @return the eastingID
* @since 1.1
*/
public char getEastingID() {
return eastingID;
}
/**
*
*
* @return isBessel
* @since 1.1
*/
public boolean isBessel() {
return isBessel;
}
/**
*
*
* @return the northing
* @since 1.1
*/
public int getNorthing() {
return northing;
}
/**
*
*
* @return the northingID
* @since 1.1
*/
public char getNorthingID() {
return northingID;
}
/**
*
*
* @return the precision
* @since 1.1
*/
public int getPrecision() {
return precision;
}
/**
*
*
* @return the utmZoneChar
* @since 1.1
*/
public char getUtmZoneChar() {
return utmZoneChar;
}
/**
*
*
* @return the utmZoneNumber
* @since 1.1
*/
public int getUtmZoneNumber() {
return utmZoneNumber;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/NotDefinedOnUTMGridException.java
================================================
package uk.me.jstott.jcoord;
/**
*
* This exception is thrown when
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 12-Mar-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NotDefinedOnUTMGridException extends RuntimeException {
/**
* Serial version UID
*/
private static final long serialVersionUID = 5699420767622348737L;
/**
* NotDefinedOnUTMGridException constructor.
*/
public NotDefinedOnUTMGridException() {
super();
}
/**
* NotDefinedOnUTMGridException constructor with a message.
*
* @param message details of the exception.
*/
public NotDefinedOnUTMGridException(String message) {
super(message);
}
}
================================================
FILE: src/uk/me/jstott/jcoord/OSRef.java
================================================
package uk.me.jstott.jcoord;
import uk.me.jstott.jcoord.datum.OSGB36Datum;
import uk.me.jstott.jcoord.ellipsoid.Airy1830Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent an Ordnance Survey of Great Britain (OSGB) grid reference.
*
*
*
* British National Grid
*
* Projection: Transverse Mercator
* Reference ellipsoid: Airy 1830
* Units: metres
* Origin: 49°N, 2°W
* False co-ordinates of origin: 400000m east, -100000m north
*
*
*
*
* A full reference includes a two-character code identifying a particular
* 100,000m grid square. The table below shows how the two-character 100,000m
* grid squares are identified. The bottom left corner is at the false origin of
* the grid. Squares without values fall outside the boundaries of the British
* National Grid.
*
*
*
*
* km
* 0
* 100
* 200
* 300
* 400
* 500
* 600
* 700
*
*
* 1200
* HL
* HM
* HN
* HO
* HP
* JL
* JM
*
*
*
* 1100
* HQ
* HR
* HS
* HT
* HU
* JQ
* JR
*
*
*
* 1000
* HV
* HW
* HX
* HY
* HZ
* JV
* JW
*
*
*
* 900
* NA
* NB
* NC
* ND
* NE
* OA
* OB
*
*
*
* 800
* NF
* NG
* NH
* NJ
* NK
* OF
* OG
* OH
*
*
* 700
* NL
* NM
* NN
* NO
* NP
* OL
* OM
* ON
*
*
* 600
* NQ
* NR
* NS
* NT
* NU
* OQ
* OR
* OS
*
*
* 500
*
* NW
* NX
* NY
* NZ
* OV
* OW
* OX
*
*
* 400
*
* SB
* SC
* SD
* SE
* TA
* TB
* TC
*
*
* 300
*
* SG
* SH
* SJ
* SK
* TF
* TG
* TH
*
*
* 200
*
* SM
* SN
* SO
* SP
* TL
* TM
* TN
*
*
* 100
* SQ
* SR
* SS
* ST
* SU
* TQ
* TR
* TS
*
*
* 0
* SV
* SW
* SX
* SY
* SZ
* TV
* TW
*
*
*
*
*
* Within each 100,000m square, the grid is further subdivided into 1000m
* squares. These 1km squares are shown on Ordnance Survey 1:25000 and 1:50000
* mapping as the main grid. To reference a 1km square, give the easting and
* then the northing, e.g. TR2266. In this example, TR represents the 100,000m
* square, 22 represents the easting (in km) and 66 represents the northing (in
* km). This is commonly called a four-figure grid reference.
*
*
*
* It is possible to extend the four-figure grid reference for more accuracy.
* For example, a six-figure grid reference would be accurate to 100m and an
* eight-figure grid reference would be accurate to 10m.
*
*
*
* When providing local references, the 2 characters representing the 100,000m
* square are often omitted.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-02-2006
*
*
* @author Jonathan Stott
* @version 1.0
* @since 1.0
*/
public class OSRef extends CoordinateSystem {
/**
* The easting in metres relative to the origin of the British National Grid.
*/
private double easting;
/**
* The northing in metres relative to the origin of the British National Grid.
*/
private double northing;
/**
* Create a new Ordnance Survey grid reference using a given easting and
* northing. The easting and northing must be in metres and must be relative
* to the origin of the British National Grid.
*
* @param easting
* the easting in metres. Must be greater than or equal to 0.0 and
* less than 800000.0.
* @param northing
* the northing in metres. Must be greater than or equal to 0.0 and
* less than 1400000.0.
* @throws IllegalArgumentException
* if either the easting or the northing are invalid.
* @since 1.0
*/
public OSRef(double easting, double northing) throws IllegalArgumentException {
super(OSGB36Datum.getInstance());
setEasting(easting);
setNorthing(northing);
}
/**
* Take a string formatted as a six-figure OS grid reference (e.g. "TG514131")
* and create a new OSRef object that represents that grid reference. The
* first character must be H, N, S, O or T. The second character can be any
* uppercase character from A through Z excluding I.
*
* @param ref
* a String representing a six-figure Ordnance Survey grid reference
* in the form XY123456
* @throws IllegalArgumentException
* if ref is not of the form XY123456
* @since 1.0
*/
public OSRef(String ref) throws IllegalArgumentException {
super(OSGB36Datum.getInstance());
// if (ref.matches(""))
// TODO 2006-02-05 : check format
char char1 = ref.charAt(0);
char char2 = ref.charAt(1);
// Thanks to Nick Holloway for pointing out the radix bug here
int east = Integer.parseInt(ref.substring(2, 5)) * 100;
int north = Integer.parseInt(ref.substring(5, 8)) * 100;
if (char1 == 'H') {
north += 1000000;
} else if (char1 == 'N') {
north += 500000;
} else if (char1 == 'O') {
north += 500000;
east += 500000;
} else if (char1 == 'T') {
east += 500000;
}
int char2ord = char2;
if (char2ord > 73)
char2ord--; // Adjust for no I
double nx = ((char2ord - 65) % 5) * 100000;
double ny = (4 - Math.floor((char2ord - 65) / 5)) * 100000;
setEasting(east + nx);
setNorthing(north + ny);
}
/**
* Convert this latitude and longitude into an OSGB (Ordnance Survey of Great
* Britain) grid reference.
*
* @return the converted OSGB grid reference.
* @since 1.1
*/
public OSRef(LatLng ll) {
super(OSGB36Datum.getInstance());
Airy1830Ellipsoid airy1830 = Airy1830Ellipsoid.getInstance();
double OSGB_F0 = 0.9996012717;
double N0 = -100000.0;
double E0 = 400000.0;
double phi0 = Math.toRadians(49.0);
double lambda0 = Math.toRadians(-2.0);
double a = airy1830.getSemiMajorAxis();
double b = airy1830.getSemiMinorAxis();
double eSquared = airy1830.getEccentricitySquared();
double phi = Math.toRadians(ll.getLatitude());
double lambda = Math.toRadians(ll.getLongitude());
double E = 0.0;
double N = 0.0;
double n = (a - b) / (a + b);
double v = a * OSGB_F0
* Math.pow(1.0 - eSquared * Util.sinSquared(phi), -0.5);
double rho = a * OSGB_F0 * (1.0 - eSquared)
* Math.pow(1.0 - eSquared * Util.sinSquared(phi), -1.5);
double etaSquared = (v / rho) - 1.0;
double M = (b * OSGB_F0)
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phi - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.sin(phi - phi0) * Math.cos(phi + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.sin(2.0 * (phi - phi0)) * Math.cos(2.0 * (phi + phi0))) - (((35.0 / 24.0)
* n * n * n)
* Math.sin(3.0 * (phi - phi0)) * Math.cos(3.0 * (phi + phi0))));
double I = M + N0;
double II = (v / 2.0) * Math.sin(phi) * Math.cos(phi);
double III = (v / 24.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 3.0)
* (5.0 - Util.tanSquared(phi) + (9.0 * etaSquared));
double IIIA = (v / 720.0) * Math.sin(phi) * Math.pow(Math.cos(phi), 5.0)
* (61.0 - (58.0 * Util.tanSquared(phi)) + Math.pow(Math.tan(phi), 4.0));
double IV = v * Math.cos(phi);
double V = (v / 6.0) * Math.pow(Math.cos(phi), 3.0)
* ((v / rho) - Util.tanSquared(phi));
double VI = (v / 120.0)
* Math.pow(Math.cos(phi), 5.0)
* (5.0 - (18.0 * Util.tanSquared(phi)) + (Math.pow(Math.tan(phi), 4.0))
+ (14 * etaSquared) - (58 * Util.tanSquared(phi) * etaSquared));
N = I + (II * Math.pow(lambda - lambda0, 2.0))
+ (III * Math.pow(lambda - lambda0, 4.0))
+ (IIIA * Math.pow(lambda - lambda0, 6.0));
E = E0 + (IV * (lambda - lambda0)) + (V * Math.pow(lambda - lambda0, 3.0))
+ (VI * Math.pow(lambda - lambda0, 5.0));
setEasting(E);
setNorthing(N);
}
/**
* Return a String representation of this OSGB grid reference showing the
* easting and northing.
*
* @return a String represenation of this OSGB grid reference
* @since 1.0
*/
public String toString() {
return "(" + easting + ", " + northing + ")";
}
/**
* Return a String representation of this OSGB grid reference using the
* six-figure notation in the form XY123456
*
* @return a String representing this OSGB grid reference in six-figure
* notation
* @since 1.0
*/
public String toSixFigureString() {
int hundredkmE = (int) Math.floor(easting / 100000);
int hundredkmN = (int) Math.floor(northing / 100000);
String firstLetter;
if (hundredkmN < 5) {
if (hundredkmE < 5) {
firstLetter = "S";
} else {
firstLetter = "T";
}
} else if (hundredkmN < 10) {
if (hundredkmE < 5) {
firstLetter = "N";
} else {
firstLetter = "O";
}
} else {
firstLetter = "H";
}
int index = 65 + ((4 - (hundredkmN % 5)) * 5) + (hundredkmE % 5);
// int ti = index;
if (index >= 73)
index++;
String secondLetter = Character.toString((char) index);
int e = (int) Math.floor((easting - (100000 * hundredkmE)) / 100);
int n = (int) Math.floor((northing - (100000 * hundredkmN)) / 100);
String es = "" + e;
if (e < 100)
es = "0" + es;
if (e < 10)
es = "0" + es;
String ns = "" + n;
if (n < 100)
ns = "0" + ns;
if (n < 10)
ns = "0" + ns;
return firstLetter + secondLetter + es + ns;
}
/**
* Convert this OSGB grid reference to a latitude/longitude pair using the
* OSGB36 datum. Note that, the LatLng object may need to be converted to the
* WGS84 datum depending on the application.
*
* @return a LatLng object representing this OSGB grid reference using the
* OSGB36 datum
* @since 1.0
*/
public LatLng toLatLng() {
double OSGB_F0 = 0.9996012717;
double N0 = -100000.0;
double E0 = 400000.0;
double phi0 = Math.toRadians(49.0);
double lambda0 = Math.toRadians(-2.0);
double a = getDatum().getReferenceEllipsoid().getSemiMajorAxis();
double b = getDatum().getReferenceEllipsoid().getSemiMinorAxis();
double eSquared = getDatum().getReferenceEllipsoid()
.getEccentricitySquared();
double phi = 0.0;
double lambda = 0.0;
double E = this.easting;
double N = this.northing;
double n = (a - b) / (a + b);
double M = 0.0;
double phiPrime = ((N - N0) / (a * OSGB_F0)) + phi0;
do {
M = (b * OSGB_F0)
* (((1 + n + ((5.0 / 4.0) * n * n) + ((5.0 / 4.0) * n * n * n)) * (phiPrime - phi0))
- (((3 * n) + (3 * n * n) + ((21.0 / 8.0) * n * n * n))
* Math.sin(phiPrime - phi0) * Math.cos(phiPrime + phi0))
+ ((((15.0 / 8.0) * n * n) + ((15.0 / 8.0) * n * n * n))
* Math.sin(2.0 * (phiPrime - phi0)) * Math
.cos(2.0 * (phiPrime + phi0))) - (((35.0 / 24.0) * n * n * n)
* Math.sin(3.0 * (phiPrime - phi0)) * Math
.cos(3.0 * (phiPrime + phi0))));
phiPrime += (N - N0 - M) / (a * OSGB_F0);
} while ((N - N0 - M) >= 0.001);
double v = a * OSGB_F0
* Math.pow(1.0 - eSquared * Util.sinSquared(phiPrime), -0.5);
double rho = a * OSGB_F0 * (1.0 - eSquared)
* Math.pow(1.0 - eSquared * Util.sinSquared(phiPrime), -1.5);
double etaSquared = (v / rho) - 1.0;
double VII = Math.tan(phiPrime) / (2 * rho * v);
double VIII = (Math.tan(phiPrime) / (24.0 * rho * Math.pow(v, 3.0)))
* (5.0 + (3.0 * Util.tanSquared(phiPrime)) + etaSquared - (9.0 * Util
.tanSquared(phiPrime) * etaSquared));
double IX = (Math.tan(phiPrime) / (720.0 * rho * Math.pow(v, 5.0)))
* (61.0 + (90.0 * Util.tanSquared(phiPrime)) + (45.0 * Util
.tanSquared(phiPrime) * Util.tanSquared(phiPrime)));
double X = Util.sec(phiPrime) / v;
double XI = (Util.sec(phiPrime) / (6.0 * v * v * v))
* ((v / rho) + (2 * Util.tanSquared(phiPrime)));
double XII = (Util.sec(phiPrime) / (120.0 * Math.pow(v, 5.0)))
* (5.0 + (28.0 * Util.tanSquared(phiPrime)) + (24.0 * Util
.tanSquared(phiPrime) * Util.tanSquared(phiPrime)));
double XIIA = (Util.sec(phiPrime) / (5040.0 * Math.pow(v, 7.0)))
* (61.0 + (662.0 * Util.tanSquared(phiPrime))
+ (1320.0 * Util.tanSquared(phiPrime) * Util.tanSquared(phiPrime)) + (720.0
* Util.tanSquared(phiPrime) * Util.tanSquared(phiPrime) * Util
.tanSquared(phiPrime)));
phi = phiPrime - (VII * Math.pow(E - E0, 2.0))
+ (VIII * Math.pow(E - E0, 4.0)) - (IX * Math.pow(E - E0, 6.0));
lambda = lambda0 + (X * (E - E0)) - (XI * Math.pow(E - E0, 3.0))
+ (XII * Math.pow(E - E0, 5.0)) - (XIIA * Math.pow(E - E0, 7.0));
return new LatLng(Math.toDegrees(phi), Math.toDegrees(lambda));
}
/**
* Get the easting in metres relative the origin of the British National Grid.
*
* @return the easting in metres.
* @since 1.0
*/
public double getEasting() {
return easting;
}
/**
* Get the northing in metres relative to the origin of the British National
* Grid.
*
* @return the northing in metres.
* @since 1.0
*/
public double getNorthing() {
return northing;
}
/**
* Set the easting for this OSRef.
*
* @param easting
* the easting in metres. Must be greater than or equal to 0.0 and
* less than 800000.0.
* @throws IllegalArgumentException
* if the easting is invalid.
*/
public void setEasting(double easting) throws IllegalArgumentException {
if (easting < 0.0 || easting >= 800000.0) {
throw new IllegalArgumentException("Easting (" + easting
+ ") is invalid. Must be greather than or equal to 0.0 and "
+ "less than 800000.0.");
}
this.easting = easting;
}
/**
* Set the northing for this OSRef
*
* @param northing
* the northing in metres. Must be greater than or equal to 0.0 and
* less than 1400000.0.
* @throws IllegalArgumentException
* if either the northing is invalid.
*/
public void setNorthing(double northing) throws IllegalArgumentException {
if (northing < 0.0 || northing >= 1400000.0) {
throw new IllegalArgumentException("Northing (" + northing
+ ") is invalid. Must be greather than or equal to 0.0 and less "
+ "than 1400000.0.");
}
this.northing = northing;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/RefEll.java
================================================
package uk.me.jstott.jcoord;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent a reference ellipsoid. Also provides a number of
* pre-determined reference ellipsoids as constants.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-Feb-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.0
* @deprecated Use classes in the uk.me.jstott.jcoord.ellipsoid package instead.
*/
public class RefEll {
/**
* Airy 1830 Reference Ellipsoid.
*
* @since 1.0
*/
public static final RefEll AIRY_1830 =
new RefEll(6377563.396,
6356256.909);
/**
* Bessel 1841 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll BESSEL_1841 =
new RefEll(6377397.155,
299.1528128);
/**
* Clarke 1866 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll CLARKE_1866 =
new RefEll(6378206.4,
294.9786982);
/**
* Clarke 1880 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll CLARKE_1880 =
new RefEll(6378249.145,
293.465);
/**
* Everest 1830 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll EVEREST_1830 =
new RefEll(6377276.345,
300.8017);
/**
* Fisher 1960 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll FISCHER_1960 = new RefEll(6378166.0, 298.3);
/**
* Fischer 1968 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll FISCHER_1968 = new RefEll(6378150.0, 298.3);
/**
* GRS 1967 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll GRS_1967 =
new RefEll(6378160.0,
298.247167427);
/**
* GRS 1975 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll GRS_1975 =
new RefEll(6378140.0,
298.257);
/**
* GRS 1980 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll GRS_1980 =
new RefEll(6378137.0,
298.257222101);
/**
* Hough 1956 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll HOUGH_1956 = new RefEll(6378270.0, 297.0);
/**
* International Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll INTERNATIONAL = new RefEll(6378388.0, 297.0);
/**
* Krassovsky 1940 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll KRASSOVSKY_1940 = new RefEll(6378245.0, 298.3);
/**
* South American 1969 Reference Ellipsoid
*
* @since 1.1
*/
public static final RefEll SOUTH_AMERICAN_1969 =
new RefEll(6378160.0,
298.25);
/**
* WGS60 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll WGS60 = new RefEll(6378165.0, 298.3);
/**
* WGS66 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll WGS66 =
new RefEll(6378145.0,
298.25);
/**
* WGS72 Reference Ellipsoid.
*
* @since 1.1
*/
public static final RefEll WGS72 =
new RefEll(6378135.0,
298.26);
/**
* WGS84 Reference Ellipsoid.
*
* @since 1.0
*/
public static final RefEll WGS84 =
new RefEll(6378137.000,
6356752.3141);
/**
* Semi-major axis
*
* @since 1.0
*/
private double maj;
/**
* Semi-minor axis
*
* @since 1.0
*/
private double min;
/**
* Eccentricity
*
* @since 1.0
*/
private double ecc;
/**
* Create a new reference ellipsoid
*
* @param maj
* semi-major axis
* @param min
* semi-minor axis
* @since 1.0
*/
public RefEll(double maj, double min) {
this.maj = maj;
this.min = min;
this.ecc = ((maj * maj) - (min * min)) / (maj * maj);
}
/**
* Return the semi-major axis.
*
* @return the semi-major axis
* @since 1.0
*/
public double getMaj() {
return maj;
}
/**
* Return the semi-minor axis
*
* @return the semi-minor axis
* @since 1.0
*/
public double getMin() {
return min;
}
/**
* Return the eccentricity.
*
* @return the eccentricity
* @since 1.0
*/
public double getEcc() {
return ecc;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/Test.java
================================================
package uk.me.jstott.jcoord;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to illustrate the use of the various functions of the classes in the
* Jcoord package.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-Feb-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.0
*/
public class Test {
/**
* Main method
*
* @param args
* not used
* @since 1.0
*/
public static void main(String[] args) {
/*
* Calculate Surface Distance between two Latitudes/Longitudes
*
* The distance() function takes a reference to a LatLng object as a
* parameter and calculates the surface distance between the the given
* object and this object in kilometres:
*/
System.out
.println("Calculate Surface Distance between two Latitudes/Longitudes");
LatLng lld1 = new LatLng(40.718119, -73.995667); // New York
System.out.println("New York Lat/Long: " + lld1.toString());
LatLng lld2 = new LatLng(51.499981, -0.125313); // London
System.out.println("London Lat/Long: " + lld2.toString());
double d = lld1.distance(lld2);
System.out.println("Surface Distance between New York and London: " + d
+ "km");
System.out.println();
/*
* Convert OS Grid Reference to Latitude/Longitude
*
* Note that the OSGB-Latitude/Longitude conversions use the OSGB36 datum by
* default. The majority of applications use the WGS84 datum, for which the
* appropriate conversions need to be added. See the examples below to see
* the difference between the two data.
*/
System.out.println("Convert OS Grid Reference to Latitude/Longitude");
// Using OSGB36 (convert an OSGB grid reference to a latitude and longitude
// using the OSGB36 datum):
System.out.println("Using OSGB36");
OSRef os1 = new OSRef(651409.903, 313177.270);
System.out.println("OS Grid Reference: " + os1.toString() + " - "
+ os1.toSixFigureString());
LatLng ll1 = os1.toLatLng();
System.out.println("Converted to Lat/Long: " + ll1.toString());
System.out.println();
// Using WGS84 (convert an OSGB grid reference to a latitude and longitude
// using the WGS84 datum):
System.out.println("Using WGS84");
OSRef os1w = new OSRef(651409.903, 313177.270);
System.out.println("OS Grid Reference: " + os1w.toString() + " - "
+ os1w.toSixFigureString());
LatLng ll1w = os1w.toLatLng();
ll1w.toWGS84();
System.out.println("Converted to Lat/Long: " + ll1w.toString());
System.out.println();
/*
* Convert Latitude/Longitude to OS Grid Reference
*
* Note that the OSGB-Latitude/Longitude conversions use the OSGB36 datum by
* default. The majority of applications use the WGS84 datum, for which the
* appropriate conversions need to be added. See the examples below to see
* the difference between the two data.
*/
System.out.println("Convert Latitude/Longitude to OS Grid Reference");
// Using OSGB36 (convert a latitude and longitude using the OSGB36 datum to
// an OSGB grid reference):
System.out.println("Using OSGB36");
LatLng ll2 = new LatLng(52.657570301933, 1.7179215806451);
System.out.println("Latitude/Longitude: " + ll2.toString());
OSRef os2 = ll2.toOSRef();
System.out.println("Converted to OS Grid Ref: " + os2.toString() + " - "
+ os2.toSixFigureString());
System.out.println();
// Using WGS84 (convert a latitude and longitude using the WGS84 datum to an
// OSGB grid reference):
System.out.println("Using WGS84");
LatLng ll2w = new LatLng(52.657570301933, 1.7179215806451);
System.out.println("Latitude/Longitude: " + ll2.toString() + " : " + ll2.toDMSString());
ll2w.toOSGB36();
OSRef os2w = ll2w.toOSRef();
System.out.println("Converted to OS Grid Ref: " + os2w.toString() + " - "
+ os2w.toSixFigureString());
System.out.println();
/*
* Convert Six-Figure OS Grid Reference String to an OSRef Object
*
* To convert a string representing a six-figure OSGB grid reference:
*/
System.out
.println("Convert Six-Figure OS Grid Reference String to an OSRef Object");
String os6 = "TG514131";
System.out.println("Six figure string: " + os6);
OSRef os6x = new OSRef(os6);
System.out.println("Converted to OS Grid Ref: " + os6x.toString() + " - "
+ os6x.toSixFigureString());
System.out.println();
/*
* Convert UTM Reference to Latitude/Longitude
*/
System.out.println("Convert UTM Reference to Latitude/Longitude");
UTMRef utm1 = new UTMRef(12, 'E', 456463.99, 3335334.05);
System.out.println("UTM Reference: " + utm1.toString());
LatLng ll3 = utm1.toLatLng();
System.out.println("Converted to Lat/Long: " + ll3.toString());
System.out.println();
/*
* Convert Latitude/Longitude to UTM Reference
*/
System.out.println("Convert Latitude/Longitude to UTM Reference");
LatLng ll4 = new LatLng(-60.1167, -111.7833);
System.out.println("Latitude/Longitude: " + ll4.toString());
UTMRef utm2 = ll4.toUTMRef();
System.out.println("Converted to UTM Ref: " + utm2.toString());
System.out.println();
mgrsTests();
}
/**
* Test the {@link MGRSRef MGRSRef} class.
*
* @since 1.1
*/
public static void mgrsTests() {
/*
* Convert UTM reference to MGRS reference
*/
System.out.println("Convert UTM Reference to MGRS Reference");
UTMRef utm1 = new UTMRef(13, 'S', 443575.71, 4349755.98);
System.out.println("UTM Reference: " + utm1.toString());
MGRSRef mgrs1 = new MGRSRef(utm1);
System.out.println("MGRS Reference: " + mgrs1.toString());
System.out.println();
/*
* Convert MGRS reference to UTM reference
*/
System.out.println("Convert MGRS reference to UTM reference");
// MGRSRef mgrs2 = new MGRSRef(13, 'S', 'D', 'D', 43576, 49756);
MGRSRef mgrs2 =
new MGRSRef(10, 'U', 'E', 'U', 0, 16300, MGRSRef.PRECISION_1M);
// 10UEU0000016300
System.out.println("MGRS Reference: " + mgrs2.toString());
UTMRef utm2 = mgrs2.toUTMRef();
System.out.println("UTM Reference: " + utm2.toString());
System.out.println();
/*
* Convert MGRS reference to Latitude/Longitude
*/
System.out.println("Convert MGRS reference to latitude/longitude");
MGRSRef mgrs3 =
new MGRSRef(13, 'S', 'D', 'D', 43575, 49756, MGRSRef.PRECISION_1M);
System.out.println("MGRS Reference: " + mgrs3.toString());
UTMRef utm5 = mgrs3.toUTMRef();
System.out.println("UTM Reference: " + utm5.toString());
LatLng ll1 = mgrs3.toLatLng();
System.out.println("Latitude/Longitude: " + ll1.toString());
System.out.println();
/*
* Convert latitude/longitude to MGRS reference
*/
System.out.println("Convert latitude/longitude to MGRS reference");
LatLng ll2 = new LatLng(39.295339, -105.654342);
//LatLng ll2 = new LatLng(48.9833, 8.2);
System.out.println("Latitude/Longitude: " + ll2.toString());
UTMRef utm3 = ll2.toUTMRef();
System.out.println("UTM Reference: " + utm3.toString());
MGRSRef mgrs4 = ll2.toMGRSRef();
System.out.println("MGRS Reference: " + mgrs4.toString());
System.out.println();
/*
* Create an MGRS reference from a String
*/
System.out.println("Create an MGRS reference from a String");
//MGRSRef mgrs5 = new MGRSRef("13SDD4357549756");
MGRSRef mgrs5 = new MGRSRef("32UMU1078");
System.out.println(mgrs5.toString(MGRSRef.PRECISION_1M));
UTMRef utm4 = mgrs5.toUTMRef();
System.out.println("UTM Reference: " + utm4.toString());
LatLng ll3 = mgrs5.toLatLng();
System.out.println("Latitude/Longitude: " + ll3.toString());
System.out.println();
//System.out.println("Convert MGRS references to UTM reference");
//for (char i = 'A'; i <= 'Z'; i++) {
// for (char j = 'A'; j <= 'V'; j++) {
// try {
// MGRSRef mgrs = new MGRSRef(1, 'A', i, j, 0, 0, MGRSRef.PRECISION_1M);
// UTMRef utm = mgrs.toUTMRef();
// System.out.println("MGRS: " + mgrs.toString());
// System.out.println(" -->: " + utm.toString());
// } catch (IllegalArgumentException e) {
// System.out.println(e.getMessage());
// }
// }
//}
}
}
================================================
FILE: src/uk/me/jstott/jcoord/UTMRef.java
================================================
package uk.me.jstott.jcoord;
import uk.me.jstott.jcoord.datum.WGS84Datum;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent a Universal Transverse Mercator (UTM) reference.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-Feb-2006
*
*
* @author Jonathan Stott
* @version 1.0
* @since 1.0
*/
public class UTMRef extends CoordinateSystem {
/**
* Easting
*/
private double easting;
/**
* Northing
*/
private double northing;
/**
* Latitude zone character
*/
private char latZone;
/**
* Longitude zone number
*/
private int lngZone;
/**
* Create a new UTM reference object. Checks are made to make sure that the
* given parameters are roughly valid, but the checks are not exhaustive with
* regards to the easting value. Catching a NotDefinedOnUTMGridException does
* not necessarily mean that the UTM reference is well-formed. This is because
* that valid values for the easting vary depending on the latitude.
*
* @param easting
* the easting in metres.
* @param northing
* the northing in metres.
* @param latZone
* the latitude zone character.
* @param lngZone
* the longitude zone number.
* @throws NotDefinedOnUTMGridException
* if any of the parameters are invalid. Be careful that a valid
* value for the easting does not necessarily mean that the UTM
* reference is well-formed. The current checks do not take into
* account the varying range of valid values for the easting for
* different latitudes.
* @since 1.0
* @deprecated Use {@link #UTMRef(int, char, double, double)}
* instead.
*/
public UTMRef(double easting, double northing, char latZone, int lngZone)
throws NotDefinedOnUTMGridException {
this(lngZone, latZone, easting, northing);
}
/**
* Create a new UTM reference object. Checks are made to make sure that the
* given parameters are roughly valid, but the checks are not exhaustive with
* regards to the easting value. Catching a NotDefinedOnUTMGridException does
* not necessarily mean that the UTM reference is well-formed. This is because
* that valid values for the easting vary depending on the latitude.
*
* @param lngZone
* the longitude zone number.
* @param latZone
* the latitude zone character.
* @param easting
* the easting in metres.
* @param northing
* the northing in metres.
* @throws NotDefinedOnUTMGridException
* if any of the parameters are invalid. Be careful that a valid
* value for the easting does not necessarily mean that the UTM
* reference is well-formed. The current checks do not take into
* account the varying range of valid values for the easting for
* different latitudes.
* @since 1.1
*/
public UTMRef(int lngZone, char latZone, double easting, double northing)
throws NotDefinedOnUTMGridException {
super(WGS84Datum.getInstance());
if (lngZone < 1 || lngZone > 60) {
throw new NotDefinedOnUTMGridException("Longitude zone (" + lngZone
+ ") is not defined on the UTM grid.");
}
if (latZone < 'C' || latZone > 'X') {
throw new NotDefinedOnUTMGridException("Latitude zone (" + latZone
+ ") is not defined on the UTM grid.");
}
if (easting < 0.0 || easting > 1000000.0) {
throw new NotDefinedOnUTMGridException("Easting (" + easting
+ ") is not defined on the UTM grid.");
}
if (northing < 0.0 || northing > 10000000.0) {
throw new NotDefinedOnUTMGridException("Northing (" + northing
+ ") is not defined on the UTM grid.");
}
this.easting = easting;
this.northing = northing;
this.latZone = latZone;
this.lngZone = lngZone;
}
/**
* Convert this UTM reference to a latitude and longitude.
*
* @return the converted latitude and longitude
* @since 1.0
*/
public LatLng toLatLng() {
double UTM_F0 = 0.9996;
double a = getDatum().getReferenceEllipsoid().getSemiMajorAxis();
double eSquared = getDatum().getReferenceEllipsoid()
.getEccentricitySquared();
double ePrimeSquared = eSquared / (1.0 - eSquared);
double e1 = (1 - Math.sqrt(1 - eSquared)) / (1 + Math.sqrt(1 - eSquared));
double x = easting - 500000.0;
;
double y = northing;
int zoneNumber = lngZone;
char zoneLetter = latZone;
double longitudeOrigin = (zoneNumber - 1.0) * 6.0 - 180.0 + 3.0;
// Correct y for southern hemisphere
if ((zoneLetter - 'N') < 0) {
y -= 10000000.0;
}
double m = y / UTM_F0;
double mu = m
/ (a * (1.0 - eSquared / 4.0 - 3.0 * eSquared * eSquared / 64.0 - 5.0 * Math
.pow(eSquared, 3.0) / 256.0));
double phi1Rad = mu + (3.0 * e1 / 2.0 - 27.0 * Math.pow(e1, 3.0) / 32.0)
* Math.sin(2.0 * mu)
+ (21.0 * e1 * e1 / 16.0 - 55.0 * Math.pow(e1, 4.0) / 32.0)
* Math.sin(4.0 * mu) + (151.0 * Math.pow(e1, 3.0) / 96.0)
* Math.sin(6.0 * mu);
double n = a
/ Math.sqrt(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad));
double t = Math.tan(phi1Rad) * Math.tan(phi1Rad);
double c = ePrimeSquared * Math.cos(phi1Rad) * Math.cos(phi1Rad);
double r = a * (1.0 - eSquared)
/ Math.pow(1.0 - eSquared * Math.sin(phi1Rad) * Math.sin(phi1Rad), 1.5);
double d = x / (n * UTM_F0);
double latitude = (phi1Rad - (n * Math.tan(phi1Rad) / r)
* (d
* d
/ 2.0
- (5.0 + (3.0 * t) + (10.0 * c) - (4.0 * c * c) - (9.0 * ePrimeSquared))
* Math.pow(d, 4.0) / 24.0 + (61.0 + (90.0 * t) + (298.0 * c)
+ (45.0 * t * t) - (252.0 * ePrimeSquared) - (3.0 * c * c))
* Math.pow(d, 6.0) / 720.0))
* (180.0 / Math.PI);
double longitude = longitudeOrigin
+ ((d - (1.0 + 2.0 * t + c) * Math.pow(d, 3.0) / 6.0 + (5.0 - (2.0 * c)
+ (28.0 * t) - (3.0 * c * c) + (8.0 * ePrimeSquared) + (24.0 * t * t))
* Math.pow(d, 5.0) / 120.0) / Math.cos(phi1Rad))
* (180.0 / Math.PI);
return new LatLng(latitude, longitude);
}
/**
* Work out the UTM latitude zone from the latitude.
*
* @param latitude
* the latitude to find the UTM latitude zone for
* @return the UTM latitude zone for the given latitude
* @since 1.0
*/
public static char getUTMLatitudeZoneLetter(double latitude) {
if ((84 >= latitude) && (latitude >= 72))
return 'X';
else if ((72 > latitude) && (latitude >= 64))
return 'W';
else if ((64 > latitude) && (latitude >= 56))
return 'V';
else if ((56 > latitude) && (latitude >= 48))
return 'U';
else if ((48 > latitude) && (latitude >= 40))
return 'T';
else if ((40 > latitude) && (latitude >= 32))
return 'S';
else if ((32 > latitude) && (latitude >= 24))
return 'R';
else if ((24 > latitude) && (latitude >= 16))
return 'Q';
else if ((16 > latitude) && (latitude >= 8))
return 'P';
else if ((8 > latitude) && (latitude >= 0))
return 'N';
else if ((0 > latitude) && (latitude >= -8))
return 'M';
else if ((-8 > latitude) && (latitude >= -16))
return 'L';
else if ((-16 > latitude) && (latitude >= -24))
return 'K';
else if ((-24 > latitude) && (latitude >= -32))
return 'J';
else if ((-32 > latitude) && (latitude >= -40))
return 'H';
else if ((-40 > latitude) && (latitude >= -48))
return 'G';
else if ((-48 > latitude) && (latitude >= -56))
return 'F';
else if ((-56 > latitude) && (latitude >= -64))
return 'E';
else if ((-64 > latitude) && (latitude >= -72))
return 'D';
else if ((-72 > latitude) && (latitude >= -80))
return 'C';
else
return 'Z';
}
/**
* Convert this UTM reference to a String representation for printing out.
*
* @return a String representation of this UTM reference
* @since 1.0
*/
public String toString() {
return lngZone + Character.toString(latZone) + " " + easting + " "
+ northing;
}
/**
* Get the easting.
*
* @return the easting
* @since 1.0
*/
public double getEasting() {
return easting;
}
/**
* Get the northing.
*
* @return the northing
* @since 1.0
*/
public double getNorthing() {
return northing;
}
/**
* Get the latitude zone character.
*
* @return the latitude zone character
* @since 1.0
*/
public char getLatZone() {
return latZone;
}
/**
* Get the longitude zone number.
*
* @return the longitude zone number
* @since 1.0
*/
public int getLngZone() {
return lngZone;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/Util.java
================================================
package uk.me.jstott.jcoord;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Some utility functions used by classes in the uk.me.jstott.jcoord package.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 11-Feb-2006
*
*
* @author Jonathan Stott
* @version 1.0
* @since 1.0
*/
class Util {
/**
* Calculate sin^2(x).
*
* @param x
* x
* @return sin^2(x)
* @since 1.0
*/
protected static double sinSquared(double x) {
return Math.sin(x) * Math.sin(x);
}
/**
* Calculate sin^3(x).
*
* @param x
* x
* @return sin^3(x)
* @since 1.1
*/
protected static double sinCubed(double x) {
return sinSquared(x) * Math.sin(x);
}
/**
* Calculate cos^2(x).
*
* @param x
* x
* @return cos^2(x)
* @since 1.0
*/
protected static double cosSquared(double x) {
return Math.cos(x) * Math.cos(x);
}
/**
* Calculate cos^3(x).
*
* @param x
* x
* @return cos^3(x)
* @since 1.1
*/
protected static double cosCubed(double x) {
return cosSquared(x) * Math.cos(x);
}
/**
* Calculate tan^2(x).
*
* @param x
* x
* @return tan^2(x)
* @since 1.0
*/
protected static double tanSquared(double x) {
return Math.tan(x) * Math.tan(x);
}
/**
* Calculate sec(x).
*
* @param x
* x
* @return sec(x)
* @since 1.0
*/
protected static double sec(double x) {
return 1.0 / Math.cos(x);
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/Datum.java
================================================
package uk.me.jstott.jcoord.datum;
import uk.me.jstott.jcoord.ellipsoid.Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* The Datum class represents a set of parameters for describing a particular
* datum, including a name, the reference ellipsoid used and the seven
* parameters required to translate co-ordinates in this datum to the WGS84
* datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 05-Mar-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public abstract class Datum {
protected String name;
protected Ellipsoid ellipsoid;
/**
* Translation along the x-axis for use in 7-parameter Helmert
* transformations. This value should be used to convert a co-ordinate in a
* given datum to the WGS84 datum.
*/
protected double dx;
/**
* Translation along the y-axis for use in 7-parameter Helmert
* transformations. This value should be used to convert a co-ordinate in a
* given datum to the WGS84 datum.
*/
protected double dy;
/**
* Translation along the z-axis for use in 7-parameter Helmert
* transformations. This value should be used to convert a co-ordinate in a
* given datum to the WGS84 datum.
*/
protected double dz;
/**
* Scale factor for use in 7-parameter Helmert transformations. This value
* should be used to convert a co-ordinate in a given datum to the WGS84
* datum.
*/
protected double ds;
/**
* Rotation about the x-axis for use in 7-parameter Helmert transformations.
* This value should be used to convert a co-ordinate in a given datum to the
* WGS84 datum.
*/
protected double rx;
/**
* Rotation about the y-axis for use in 7-parameter Helmert transformations.
* This value should be used to convert a co-ordinate in a given datum to the
* WGS84 datum.
*/
protected double ry;
/**
* Rotation about the z-axis for use in 7-parameter Helmert transformations.
* This value should be used to convert a co-ordinate in a given datum to the
* WGS84 datum.
*/
protected double rz;
/**
* Get the name of this Datum.
*
* @return the name of this Datum.
* @since 1.1
*/
public String getName() {
return name;
}
/**
* Get the reference ellipsoid associated with this Datum.
*
* @return the reference ellipsoid associated with this Datum.
* @since 1.1
*/
public Ellipsoid getReferenceEllipsoid() {
return ellipsoid;
}
/**
*
*
* @return the ds
* @since 1.1
*/
public double getDs() {
return ds;
}
/**
*
*
* @return the dx
* @since 1.1
*/
public double getDx() {
return dx;
}
/**
*
*
* @return the dy
* @since 1.1
*/
public double getDy() {
return dy;
}
/**
*
*
* @return the dz
* @since 1.1
*/
public double getDz() {
return dz;
}
/**
*
*
* @return the rx
* @since 1.1
*/
public double getRx() {
return rx;
}
/**
*
*
* @return the ry
* @since 1.1
*/
public double getRy() {
return ry;
}
/**
*
*
* @return the rz
* @since 1.1
*/
public double getRz() {
return rz;
}
/**
* Get a String representation of the parameters of a Datum object.
*
* @return a String representation of the parameters of a Datum object.
* @since 1.1
*/
public String toString() {
return getName() + " " + ellipsoid.toString() + " dx=" + dx + " dy=" + dy
+ " dz=" + dz + " ds=" + ds + " rx=" + rx + " ry=" + ry + " rz=" + rz;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/ETRF89Datum.java
================================================
package uk.me.jstott.jcoord.datum;
import uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 03-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class ETRF89Datum extends Datum {
/**
* Static reference of this datum.
*/
private static ETRF89Datum ref = null;
/**
* Create a new ETRF89Datum object.
*
* @since 1.1
*/
private ETRF89Datum() {
name = "European Terrestrial Reference Frame (ETRF89)";
ellipsoid = WGS84Ellipsoid.getInstance();
dx = 0.0;
dy = 0.0;
dz = 0.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static ETRF89Datum getInstance() {
if (ref == null) {
ref = new ETRF89Datum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/Ireland1965Datum.java
================================================
package uk.me.jstott.jcoord.datum;
import uk.me.jstott.jcoord.ellipsoid.ModifiedAiryEllipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 03-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Ireland1965Datum extends Datum {
/**
* Static reference of this datum.
*/
private static Ireland1965Datum ref = null;
/**
* Create a new Ireland 1965 Datum object.
*
* @since 1.1
*/
private Ireland1965Datum() {
name = "Ireland 1965";
ellipsoid = ModifiedAiryEllipsoid.getInstance();
dx = 482.53;
dy = -130.596;
dz = 564.557;
ds = 8.15;
rx = -1.042;
ry = -0.214;
rz = -0.631;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static Ireland1965Datum getInstance() {
if (ref == null) {
ref = new Ireland1965Datum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/OSGB36Datum.java
================================================
package uk.me.jstott.jcoord.datum;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 05-Mar-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class OSGB36Datum extends Datum {
/**
* Static reference of this datum.
*/
private static OSGB36Datum ref = null;
/**
* Create a new OSGB36 object.
*
* @since 1.1
*/
private OSGB36Datum() {
name = "Ordnance Survey of Great Britain 1936 (OSGB36)";
ellipsoid = uk.me.jstott.jcoord.ellipsoid.Airy1830Ellipsoid.getInstance();
dx = 446.448;
dy = -125.157;
dz = 542.06;
ds = -20.4894;
rx = 0.1502;
ry = 0.2470;
rz = 0.8421;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static OSGB36Datum getInstance() {
if (ref == null) {
ref = new OSGB36Datum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/WGS84Datum.java
================================================
package uk.me.jstott.jcoord.datum;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class WGS84Datum extends Datum {
/**
* Static reference of this datum.
*/
private static WGS84Datum ref = null;
/**
* Create a new WGS84 object.
*
* @since 1.1
*/
public WGS84Datum() {
name = "World Geodetic System 1984 (WGS84)";
ellipsoid = uk.me.jstott.jcoord.ellipsoid.WGS84Ellipsoid.getInstance();
dx = 0.0;
dy = 0.0;
dz = 0.0;
ds = 1.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static WGS84Datum getInstance() {
if (ref == null) {
ref = new WGS84Datum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27AlaskaDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Alaska) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27AlaskaDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27AlaskaDatum ref = null;
/**
* Create a new NAD27 (Alaska) datum object.
*
* @since 1.1
*/
private NAD27AlaskaDatum() {
name = "North American Datum 1927 (NAD27) - Alaska";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -5.0;
dy = 135.0;
dz = 172.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27AlaskaDatum getInstance() {
if (ref == null) {
ref = new NAD27AlaskaDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27AlbertaBritishColumbiaDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Alberta and British Columbia) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27AlbertaBritishColumbiaDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27AlbertaBritishColumbiaDatum ref = null;
/**
* Create a new NAD27 (Alberta and British Columbia) datum object.
*
* @since 1.1
*/
private NAD27AlbertaBritishColumbiaDatum() {
name = "North American Datum 1927 (NAD27) - Alberta and British Columbia";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -7.0;
dy = 162.0;
dz = 188.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27AlbertaBritishColumbiaDatum getInstance() {
if (ref == null) {
ref = new NAD27AlbertaBritishColumbiaDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27AleutianEastDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Aleutian East) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27AleutianEastDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27AleutianEastDatum ref = null;
/**
* Create a new NAD27 (Aleutian East) datum object.
*
* @since 1.1
*/
private NAD27AleutianEastDatum() {
name = "North American Datum 1927 (NAD27) - Aleutian East";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -2.0;
dy = 152.0;
dz = 149.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27AleutianEastDatum getInstance() {
if (ref == null) {
ref = new NAD27AleutianEastDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27AleutianWestDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Aleutian West) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27AleutianWestDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27AleutianWestDatum ref = null;
/**
* Create a new NAD27 (Aleutian West) datum object.
*
* @since 1.1
*/
private NAD27AleutianWestDatum() {
name = "North American Datum 1927 (NAD27) - Aleutian West";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = 2.0;
dy = 204.0;
dz = 105.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27AleutianWestDatum getInstance() {
if (ref == null) {
ref = new NAD27AleutianWestDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27BahamasDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Bahamas) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27BahamasDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27BahamasDatum ref = null;
/**
* Create a new NAD27 (Bahamas) datum object.
*
* @since 1.1
*/
private NAD27BahamasDatum() {
name = "North American Datum 1927 (NAD27) - Bahamas";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -4.0;
dy = 154.0;
dz = 178.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27BahamasDatum getInstance() {
if (ref == null) {
ref = new NAD27BahamasDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CanadaDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Canada) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CanadaDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CanadaDatum ref = null;
/**
* Create a new NAD27 (Canada) datum object.
*
* @since 1.1
*/
private NAD27CanadaDatum() {
name = "North American Datum 1927 (NAD27) - Canada";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -10.0;
dy = 158.0;
dz = 187.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CanadaDatum getInstance() {
if (ref == null) {
ref = new NAD27CanadaDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CanadaEastDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Canada East) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CanadaEastDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CanadaEastDatum ref = null;
/**
* Create a new NAD27 (Canada East) datum object.
*
* @since 1.1
*/
private NAD27CanadaEastDatum() {
name = "North American Datum 1927 (NAD27) - Canada East";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -22.0;
dy = 160.0;
dz = 190.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CanadaEastDatum getInstance() {
if (ref == null) {
ref = new NAD27CanadaEastDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CanadaManitobaOntarioDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Canada Manitoba/Ontario) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CanadaManitobaOntarioDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CanadaManitobaOntarioDatum ref = null;
/**
* Create a new NAD27 (Canada Manitoba/Ontario) datum object.
*
* @since 1.1
*/
private NAD27CanadaManitobaOntarioDatum() {
name = "North American Datum 1927 (NAD27) - Canada Manitoba/Ontario";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -9.0;
dy = 157.0;
dz = 184.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CanadaManitobaOntarioDatum getInstance() {
if (ref == null) {
ref = new NAD27CanadaManitobaOntarioDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CanadaNWTerritoryDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Canada NW Territory) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CanadaNWTerritoryDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CanadaNWTerritoryDatum ref = null;
/**
* Create a new NAD27 (Canada NW Territory) datum object.
*
* @since 1.1
*/
private NAD27CanadaNWTerritoryDatum() {
name = "North American Datum 1927 (NAD27) - Canada NW Territory";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = 4.0;
dy = 159.0;
dz = 188.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CanadaNWTerritoryDatum getInstance() {
if (ref == null) {
ref = new NAD27CanadaNWTerritoryDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CanadaYukonDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Canada Yukon) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CanadaYukonDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CanadaYukonDatum ref = null;
/**
* Create a new NAD27 (Canada Yukon) datum object.
*
* @since 1.1
*/
private NAD27CanadaYukonDatum() {
name = "North American Datum 1927 (NAD27) - Canada Yukon";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -7.0;
dy = 139.0;
dz = 181.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CanadaYukonDatum getInstance() {
if (ref == null) {
ref = new NAD27CanadaYukonDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CanalZoneDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Canal Zone) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CanalZoneDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CanalZoneDatum ref = null;
/**
* Create a new NAD27 (Canal Zone) datum object.
*
* @since 1.1
*/
private NAD27CanalZoneDatum() {
name = "North American Datum 1927 (NAD27) - Canal Zone";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = 0.0;
dy = 125.0;
dz = 201.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CanalZoneDatum getInstance() {
if (ref == null) {
ref = new NAD27CanalZoneDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CaribbeanDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Caribbean) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CaribbeanDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CaribbeanDatum ref = null;
/**
* Create a new NAD27 (Caribbean) datum object.
*
* @since 1.1
*/
private NAD27CaribbeanDatum() {
name = "North American Datum 1927 (NAD27) - Caribbean";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -3.0;
dy = 142.0;
dz = 183.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CaribbeanDatum getInstance() {
if (ref == null) {
ref = new NAD27CaribbeanDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CentralAmericaDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Central America) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CentralAmericaDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CentralAmericaDatum ref = null;
/**
* Create a new NAD27 (Alaska) datum object.
*
* @since 1.1
*/
private NAD27CentralAmericaDatum() {
name = "North American Datum 1927 (NAD27) - Central America";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = 0.0;
dy = 125.0;
dz = 194.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CentralAmericaDatum getInstance() {
if (ref == null) {
ref = new NAD27CentralAmericaDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27ContiguousUSDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Contiguous United States) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27ContiguousUSDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27ContiguousUSDatum ref = null;
/**
* Create a new NAD27 (Contiguous United States) datum object.
*
* @since 1.1
*/
private NAD27ContiguousUSDatum() {
name = "North American Datum 1927 (NAD27) - Contiguous United States";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -8.0;
dy = 160.0;
dz = 176.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27ContiguousUSDatum getInstance() {
if (ref == null) {
ref = new NAD27ContiguousUSDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27CubaDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Cuba) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27CubaDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27CubaDatum ref = null;
/**
* Create a new NAD27 (Cuba) datum object.
*
* @since 1.1
*/
private NAD27CubaDatum() {
name = "North American Datum 1927 (NAD27) - Cuba";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -9.0;
dy = 152.0;
dz = 178.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27CubaDatum getInstance() {
if (ref == null) {
ref = new NAD27CubaDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27EasternUSDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Eastern US) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27EasternUSDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27EasternUSDatum ref = null;
/**
* Create a new NAD27 (Eastern US) datum object.
*
* @since 1.1
*/
private NAD27EasternUSDatum() {
name = "North American Datum 1927 (NAD27) - Eastern US";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -9.0;
dy = 161.0;
dz = 179.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27EasternUSDatum getInstance() {
if (ref == null) {
ref = new NAD27EasternUSDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27GreenlandDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Greenland) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27GreenlandDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27GreenlandDatum ref = null;
/**
* Create a new NAD27 (Greenland) datum object.
*
* @since 1.1
*/
private NAD27GreenlandDatum() {
name = "North American Datum 1927 (NAD27) - Greenland";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = 11.0;
dy = 114.0;
dz = 195.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27GreenlandDatum getInstance() {
if (ref == null) {
ref = new NAD27GreenlandDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27MexicoDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Mexico) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27MexicoDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27MexicoDatum ref = null;
/**
* Create a new NAD27 (Mexico) datum object.
*
* @since 1.1
*/
private NAD27MexicoDatum() {
name = "North American Datum 1927 (NAD27) - Mexico";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -12.0;
dy = 130.0;
dz = 190.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27MexicoDatum getInstance() {
if (ref == null) {
ref = new NAD27MexicoDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27SanSalvadorDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (San Salvador) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27SanSalvadorDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27SanSalvadorDatum ref = null;
/**
* Create a new NAD27 (San Salvador) datum object.
*
* @since 1.1
*/
private NAD27SanSalvadorDatum() {
name = "North American Datum 1927 (NAD27) - San Salvador";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = 1.0;
dy = 140.0;
dz = 165.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27SanSalvadorDatum getInstance() {
if (ref == null) {
ref = new NAD27SanSalvadorDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/datum/nad27/NAD27WesternUSDatum.java
================================================
package uk.me.jstott.jcoord.datum.nad27;
import uk.me.jstott.jcoord.datum.Datum;
import uk.me.jstott.jcoord.ellipsoid.Clarke1866Ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class representing the NAD27 (Western US) datum.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NAD27WesternUSDatum extends Datum {
/**
* Static reference of this datum.
*/
private static NAD27WesternUSDatum ref = null;
/**
* Create a new NAD27 (Western US) datum object.
*
* @since 1.1
*/
private NAD27WesternUSDatum() {
name = "North American Datum 1927 (NAD27) - Western US";
ellipsoid = Clarke1866Ellipsoid.getInstance();
dx = -8.0;
dy = 159.0;
dz = 175.0;
ds = 0.0;
rx = 0.0;
ry = 0.0;
rz = 0.0;
}
/**
* Get the static instance of this datum
*
* @return a reference to the static instance of this datum
* @since 1.1
*/
public static NAD27WesternUSDatum getInstance() {
if (ref == null) {
ref = new NAD27WesternUSDatum();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Airy1830Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Airy 1830 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Airy1830Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Airy1830Ellipsoid ref = null;
/**
* Create an object defining the Airy 1830 reference ellipsoid.
*
* @since 1.1
*/
private Airy1830Ellipsoid() {
super(6377563.396, 6356256.909);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Airy1830Ellipsoid getInstance() {
if (ref == null) {
ref = new Airy1830Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/AustralianNational1966Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Australian National 1966 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class AustralianNational1966Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static AustralianNational1966Ellipsoid ref = null;
/**
* Create an object defining the Australian National 1966 reference ellipsoid.
*
* @since 1.1
*/
private AustralianNational1966Ellipsoid() {
super(6378160.0, 6356774.719);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static AustralianNational1966Ellipsoid getInstance() {
if (ref == null) {
ref = new AustralianNational1966Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Bessel1841Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Bessel 1841 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Bessel1841Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Bessel1841Ellipsoid ref = null;
/**
* Create an object defining the Bessel 1841 reference ellipsoid.
*
* @since 1.1
*/
private Bessel1841Ellipsoid() {
super(6377397.155, 6356078.9629);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Bessel1841Ellipsoid getInstance() {
if (ref == null) {
ref = new Bessel1841Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Clarke1866Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Clarke 1866 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Clarke1866Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Clarke1866Ellipsoid ref = null;
/**
* Create an object defining the Clarke 1866 reference ellipsoid.
*
* @since 1.1
*/
private Clarke1866Ellipsoid() {
super(6378206.4, 6356583.8);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Clarke1866Ellipsoid getInstance() {
if (ref == null) {
ref = new Clarke1866Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Clarke1880Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Clarke 1880 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Clarke1880Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Clarke1880Ellipsoid ref = null;
/**
* Create an object defining the Clarke 1880 reference ellipsoid.
*
* @since 1.1
*/
private Clarke1880Ellipsoid() {
super(6378249.145, 6356514.8696);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Clarke1880Ellipsoid getInstance() {
if (ref == null) {
ref = new Clarke1880Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class to represent a reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public abstract class Ellipsoid {
/**
* Semi major axis.
*/
protected double semiMajorAxis;
/**
* Semi minor axis.
*/
protected double semiMinorAxis;
/**
* Eccentricity squared.
*/
protected double eccentricitySquared;
/**
* Flattening.
*/
protected double flattening;
/**
* Create a new ellipsoid with the given parameters.
*
* @param semiMajorAxis
* the semi major axis.
* @param semiMinorAxis
* the semi minor axis.
* @since 1.1
*/
public Ellipsoid(double semiMajorAxis, double semiMinorAxis) {
this.semiMajorAxis = semiMajorAxis;
this.semiMinorAxis = semiMinorAxis;
double semiMajorAxisSquared = semiMajorAxis * semiMajorAxis;
double semiMinorAxisSquared = semiMinorAxis * semiMinorAxis;
flattening = (semiMajorAxis - semiMinorAxis) / semiMajorAxis;
eccentricitySquared = (semiMajorAxisSquared - semiMinorAxisSquared)
/ semiMajorAxisSquared;
}
/**
* Create a new ellipsoid with the given parameters. If either the
* semiMinorAxis or the eccentricitySquared are Double.NaN, then that value is
* calculated from the other two parameters. An IllegalArgumentException is
* thrown if both the semiMinorAxis and the eccentricitySquared are
* Double.NaN.
*
* @param semiMajorAxis
* the semi major axis.
* @param semiMinorAxis
* the semi minor axis.
* @param eccentricitySquared
* the eccentricity squared.
* @throws IllegalArgumentException
* is both the semiMinorAxis and eccentricitySquared parameters are
* Double.NaN.
* @since 1.1
*/
public Ellipsoid(double semiMajorAxis, double semiMinorAxis,
double eccentricitySquared) throws IllegalArgumentException {
if (Double.isNaN(semiMinorAxis) && Double.isNaN(eccentricitySquared)) {
throw new IllegalArgumentException(
"At least one of semiMinorAxis and eccentricitySquared must be defined");
}
this.semiMajorAxis = semiMajorAxis;
double semiMajorAxisSquared = semiMajorAxis * semiMajorAxis;
if (Double.isNaN(semiMinorAxis)) {
this.semiMinorAxis = Math.sqrt(semiMajorAxisSquared
* (1 - eccentricitySquared));
} else {
this.semiMinorAxis = semiMinorAxis;
}
double semiMinorAxisSquared = this.semiMinorAxis * this.semiMinorAxis;
flattening = (this.semiMajorAxis - this.semiMinorAxis) / this.semiMajorAxis;
if (Double.isNaN(eccentricitySquared)) {
this.eccentricitySquared = (semiMajorAxisSquared - semiMinorAxisSquared)
/ semiMajorAxisSquared;
} else {
this.eccentricitySquared = eccentricitySquared;
}
}
/**
* Get a String representation of the Ellipsoid
*
* @return a String representation of the Ellipsoid
* @since 1.1
*/
public String toString() {
return "[semi-major axis = " + getSemiMajorAxis() + ", semi-minor axis = "
+ getSemiMinorAxis() + "]";
}
/**
* Get the eccentricity squared.
*
* @return Returns the eccentricitySquared.
* @since 1.1
*/
public double getEccentricitySquared() {
return eccentricitySquared;
}
/**
* Get the flattening.
*
* @return Returns the flattening.
* @since 1.1
*/
public double getFlattening() {
return flattening;
}
/**
* Get the semi major axis.
*
* @return Returns the semiMajorAxis.
* @since 1.1
*/
public double getSemiMajorAxis() {
return semiMajorAxis;
}
/**
* Get the semi minor axis.
*
* @return Returns the semiMinorAxis.
* @since 1.1
*/
public double getSemiMinorAxis() {
return semiMinorAxis;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/EverestEllipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Everest 1830 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class EverestEllipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static EverestEllipsoid ref = null;
/**
* Create an object defining the Everest 1830 reference ellipsoid.
*
* @since 1.1
*/
private EverestEllipsoid() {
super(6377276.34518, 6356075.41511);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static EverestEllipsoid getInstance() {
if (ref == null) {
ref = new EverestEllipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Fischer1960Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Fischer 1960 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Fischer1960Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Fischer1960Ellipsoid ref = null;
/**
* Create an object defining the Fischer 1960 reference ellipsoid.
*
* @since 1.1
*/
private Fischer1960Ellipsoid() {
super(6378166.0, 6356784.284);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Fischer1960Ellipsoid getInstance() {
if (ref == null) {
ref = new Fischer1960Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Fischer1968Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Fischer 1968 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Fischer1968Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Fischer1968Ellipsoid ref = null;
/**
* Create an object defining the Fischer 1968 reference ellipsoid.
*
* @since 1.1
*/
private Fischer1968Ellipsoid() {
super(6378150.0, 6356768.337);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Fischer1968Ellipsoid getInstance() {
if (ref == null) {
ref = new Fischer1968Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/GRS67Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the GRS67 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class GRS67Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static GRS67Ellipsoid ref = null;
/**
* Create an object defining the GRS67 reference ellipsoid.
*
* @since 1.1
*/
private GRS67Ellipsoid() {
super(6378160.0, 6356774.51609);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static GRS67Ellipsoid getInstance() {
if (ref == null) {
ref = new GRS67Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/GRS75Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the GRS75 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class GRS75Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static GRS75Ellipsoid ref = null;
/**
* Create an object defining the GRS75 reference ellipsoid.
*
* @since 1.1
*/
private GRS75Ellipsoid() {
super(6378140.0, 6356755.288);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static GRS75Ellipsoid getInstance() {
if (ref == null) {
ref = new GRS75Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/GRS80Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the GRS80 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class GRS80Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static GRS80Ellipsoid ref = null;
/**
* Create an object defining the GRS80 reference ellipsoid.
*
* @since 1.1
*/
private GRS80Ellipsoid() {
super(6378137, 6356752.3141);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static GRS80Ellipsoid getInstance() {
if (ref == null) {
ref = new GRS80Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Hayford1910Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Hayford 1910 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Hayford1910Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Hayford1910Ellipsoid ref = null;
/**
* Create an object defining the Hayford 1910 reference ellipsoid.
*
* @since 1.1
*/
private Hayford1910Ellipsoid() {
super(6378388.0, 6356911.946);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Hayford1910Ellipsoid getInstance() {
if (ref == null) {
ref = new Hayford1910Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Helmert1906Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Helmert 1906 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Helmert1906Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Helmert1906Ellipsoid ref = null;
/**
* Create an object defining the Helmert 1906 reference ellipsoid.
*
* @since 1.1
*/
private Helmert1906Ellipsoid() {
super(6378200.0, 6356818.17);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Helmert1906Ellipsoid getInstance() {
if (ref == null) {
ref = new Helmert1906Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Hough1956Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Hough 1956 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Hough1956Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Hough1956Ellipsoid ref = null;
/**
* Create an object defining the Hough 1956 reference ellipsoid.
*
* @since 1.1
*/
private Hough1956Ellipsoid() {
super(6378270.0, 6356794.34);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Hough1956Ellipsoid getInstance() {
if (ref == null) {
ref = new Hough1956Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/IERS1989Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the IERS 1989 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class IERS1989Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static IERS1989Ellipsoid ref = null;
/**
* Create an object defining the IERS 1989 reference ellipsoid.
*
* @since 1.1
*/
private IERS1989Ellipsoid() {
super(6378136.0, 6356751.302);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static IERS1989Ellipsoid getInstance() {
if (ref == null) {
ref = new IERS1989Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/InternationalEllipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the International reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class InternationalEllipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static InternationalEllipsoid ref = null;
/**
* Create an object defining the International reference ellipsoid.
*
* @since 1.1
*/
private InternationalEllipsoid() {
super(6378388, 6356911.9462);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static InternationalEllipsoid getInstance() {
if (ref == null) {
ref = new InternationalEllipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/Krassovsky1940Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Krassovsky 1940 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class Krassovsky1940Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static Krassovsky1940Ellipsoid ref = null;
/**
* Create an object defining the Krassovsky 1940 reference ellipsoid.
*
* @since 1.1
*/
private Krassovsky1940Ellipsoid() {
super(6378245.0, 6356863.019);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static Krassovsky1940Ellipsoid getInstance() {
if (ref == null) {
ref = new Krassovsky1940Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/ModifiedAiryEllipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Modified Airy reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class ModifiedAiryEllipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static ModifiedAiryEllipsoid ref = null;
/**
* Create an object defining the Modified Airy reference ellipsoid.
*
* @since 1.1
*/
private ModifiedAiryEllipsoid() {
super(6377340.189, Double.NaN, 0.00667054015);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static ModifiedAiryEllipsoid getInstance() {
if (ref == null) {
ref = new ModifiedAiryEllipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/ModifiedEverestEllipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the Modified Everest reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class ModifiedEverestEllipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static ModifiedEverestEllipsoid ref = null;
/**
* Create an object defining the Modified Everest reference ellipsoid.
*
* @since 1.1
*/
public ModifiedEverestEllipsoid() {
super(6377304.063, 6356103.039);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static ModifiedEverestEllipsoid getInstance() {
if (ref == null) {
ref = new ModifiedEverestEllipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/NewInternational1967Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the New International 1967 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class NewInternational1967Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static NewInternational1967Ellipsoid ref = null;
/**
* Create an object defining the Ne wInternational 1967 reference ellipsoid.
*
* @since 1.1
*/
private NewInternational1967Ellipsoid() {
super(6378157.5, 6356772.2);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static NewInternational1967Ellipsoid getInstance() {
if (ref == null) {
ref = new NewInternational1967Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/SouthAmerican1969Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the South American 1969 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class SouthAmerican1969Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static SouthAmerican1969Ellipsoid ref = null;
/**
* Create an object defining the South American 1969 reference ellipsoid.
*
* @since 1.1
*/
private SouthAmerican1969Ellipsoid() {
super(6378160.0, 6356774.7192);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static SouthAmerican1969Ellipsoid getInstance() {
if (ref == null) {
ref = new SouthAmerican1969Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/WGS60Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the WGS60 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class WGS60Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static WGS60Ellipsoid ref = null;
/**
* Create an object defining the WGS60 reference ellipsoid.
*
* @since 1.1
*/
private WGS60Ellipsoid() {
super(6378165.0, 6356783.287);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static WGS60Ellipsoid getInstance() {
if (ref == null) {
ref = new WGS60Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/WGS66Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the WGS66 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class WGS66Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static WGS66Ellipsoid ref = null;
/**
* Create an object defining the WGS66 reference ellipsoid.
*
* @since 1.1
*/
private WGS66Ellipsoid() {
super(6378145.0, 6356759.770);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static WGS66Ellipsoid getInstance() {
if (ref == null) {
ref = new WGS66Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/WGS72Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the WGS72 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class WGS72Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static WGS72Ellipsoid ref = null;
/**
* Create an object defining the WGS72 reference ellipsoid.
*
* @since 1.1
*/
private WGS72Ellipsoid() {
super(6378135, 6356750.5);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static WGS72Ellipsoid getInstance() {
if (ref == null) {
ref = new WGS72Ellipsoid();
}
return ref;
}
}
================================================
FILE: src/uk/me/jstott/jcoord/ellipsoid/WGS84Ellipsoid.java
================================================
package uk.me.jstott.jcoord.ellipsoid;
/**
*
* This class is part of the Jcoord package. Visit the Jcoord website for more
* information.
*
*
*
* Class defining the WGS84 reference ellipsoid.
*
*
*
* (c) 2006 Jonathan Stott
*
*
*
* Created on 02-Apr-2006
*
*
* @author Jonathan Stott
* @version 1.1
* @since 1.1
*/
public class WGS84Ellipsoid extends Ellipsoid {
/**
* Static reference of this ellipsoid.
*/
private static WGS84Ellipsoid ref = null;
/**
* Create an object defining a WGS84 reference ellipsoid.
*
* @since 1.1
*/
private WGS84Ellipsoid() {
super(6378137, 6356752.3142);
}
/**
* Get the static instance of this ellipsoid
*
* @return a reference to the static instance of this ellipsoid
* @since 1.1
*/
public static WGS84Ellipsoid getInstance() {
if (ref == null) {
ref = new WGS84Ellipsoid();
}
return ref;
}
}