With all due respect, your data is boring.
So take advantage of that.
Leverage its very boring nature.
I recommend you send such data in routine Update messages.
And that you occasionally send Baseline messages,
perhaps a few times each minute.
Send each given Baseline message more than once,
in case of occasional packet loss,
since it's needed to properly decode other Update messages.
Give each distinct Baseline message an incrementing serial number,
and send the serial as part of each Update.
The analagous MPEG concept is
key frames or
I-frames.
A GPS coordinate needs several bits to represent any
location on the surface of the Earth.
But you know roughly where your balloon is.
So offer detailed (lat, long) information in each Baseline message,
and then Updates only need to express a small delta,
typically less than 1000 meters.
For that matter, you might find it convenient to have
the onboard Raspberry Pi translate from degrees,
and communicate in units of meters.
The raw temperature and pressure data will be sent in
each Baseline message.
But you explained that what you really use them for
is altitude. (And apparently the notoriously bad GPS Z-coordinate
has such low resolution that you don't even bother to send it.)
Clearly you have some altitude(temp, press)
formula
that post-processing will run on the ground, to get
a time series of (x, y, z) coordinates.
Consider computing such altitudes on-board,
include it in each Baseline, and send only
that computed altitude in each Update,
so temperature and pressure are elided from Updates.
More generally, your balloon's physical position corresponds
to a smoothly differentiable function of time. We can wrap a
Kalman filter
around it.
Suppose we know a recent (vx, vy, vz) velocity vector
from a previous time step.
It is a very good estimate for what the vector will be
in the next time step.
And even if it's off, it will be off by only a small delta,
which can be transmitted with fewer bits.
summary
Send all the data that you're currently sending
in occasional Baseline messages.
Compute altitude on-board.
Compute 3-space position, and velocity vector, on-board,
and perhaps model them with a Kalman filter.
Send small deltas in frequent Update messages.
triggered updates
An isochronous communication schedule would have boring
timestamps, like {noon + .1s, noon + .2s, ..., noon + .9s}.
Sometimes your RPi interrogates a sensor, and nothing changed.
(For example, humidity probably doesn't need to go in Updates,
and might appear many times with same value in successive Baselines.)
Sending "delta was zero", "delta was zero" consumes bandwidth
while communicating very little beyond "payload is still powered up".
Each GPS reading might be distinct, but the low order bits
might be very noisy, something you'd like to smooth out
on the ground and perhaps on-board.
This might refine your notion of whether repeated
observations were "distinct".
If you query your sensors, and find that "nothing changed"
since last time, consider suppressing a boring Update message.
Keep polling, and re-polling, till the instant something changes --
then you send an immediate Update.
Now the timestamp is actually meaningful,
it lets you increase the resolution of your measurements,
by narrowing down just when a sensor incremented its reported value.
Consider having two types of Update message, "fast" and "slow",
which report on different sensors according to their
anticipated rate of change.
modeling
Notice that you can replay your logs of previous historic
balloon flights when trying to incorporate these ideas.
Choose certain number of bits, or update strategies,
try it against logged data to see how compression performs,
and then tweak the parts you're not yet happy with.
simple compression
Suppose you wish to change your stack as little as possible.
You can still stick with JSON + LZW.
One improvement is a standard dictionary.
Create a short boilerplate JSON document that mentions
the names "GPS", "Accelerometer", etc.,
and always play that as the first part of
what you send, before the real JSON payload data.
It populates the LZW dictionary.
It is constant, so it produces constant compressed output.
Subsequent JSON text can exploit the dictionary references.
Compute compress(boiler + data1)
and compress(boiler + data2)
.
Compare the bytes and notice the common prefix.
Write a layer, a pair of functions which can strip and restore
that common prefix.
Now the packets you send every 100 msec are shorter.
One step fancier is to replace fixed boilerplate
with data values corresponding to a point in
a scheduled balloon flight.
And fancier than that would be to send occasional Baseline
documents reflecting "a point we visited ten seconds ago"
along with Update documents which use them.
So if humidity doesn't change for ten seconds,
the humidity field gets squeezed right out of most Updates.
images
Video data versus telemetry is a whole other ball of wax,
orders of magnitude more data.
You already store images onboard in hopes of
later recovering the balloon, and clearly that
will remain your biggest source of imagery bandwidth.
Go review historic images taken on previous flights,
and decide which images you would find most valuable
if they were available in near realtime.
I'm guessing that images early in the flight
are relatively uninteresting,
and images just prior to landing will be most informative,
as an aid to recovering the balloon.
So maybe we send no image data in the first
few minutes of any flight.
Now take an image you wish you'd had in realtime,
convert to grayscale, and squish its JPEG resolution.
Be sure to use progressive rendering.
Determine how far you can squish it while still
being valuable.
Now pick a 2nd or 3rd such image.
Maybe they correspond to the last two or three
minutes of flight? And we'll try to send one per minute?
During flight, snap an image and chop it up into segments
that take about one second to send.
Alternate between sending segments and sending Update messages.
Once the balloon is on the ground, does it still have
decent bandwidth to the chase car?
It could continue to send "final descent" images
for a few minutes while it awaits pickup.
radio bandwidth
You mentioned that you're being conservative in how
you configure your radio.
Consider having low- & high-risk segments of a mission.
You are nearly guaranteed to obtain some realtime
data early on in the low-risk portion, even if
the chase car finds the balloon at the bottom of a lake.
And then you cross your fingers, increase the
transmission rate, and start sending image data
during the high-risk portion.
You receive those images, or you don't.
Nothing ventured, nothing gained.
Maybe have the RPi reduce the transmission rate,
for lower risk, when it falls below some
threshold altitude.
That should give you a better chance of sending
GPS coords to the chase car,
while you're still high enough to enjoy
good radio signal propagation.